package org.jacorb.orb.iiop;
/*
* JacORB - a free Java ORB
*
* Copyright (C) 1999-2004 Gerald Brose
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import org.jacorb.config.*;
import org.jacorb.orb.BasicAdapter;
import org.jacorb.orb.etf.ProtocolAddressBase;
import org.jacorb.orb.factory.SocketFactoryManager;
import org.jacorb.orb.listener.AcceptorExceptionEvent;
import org.jacorb.orb.listener.AcceptorExceptionListener;
import org.jacorb.orb.listener.DefaultAcceptorExceptionListener;
import org.jacorb.orb.listener.SSLListenerUtil;
import org.jacorb.orb.listener.TCPConnectionEvent;
import org.jacorb.orb.listener.TCPConnectionListener;
import org.omg.CSIIOP.Confidentiality;
import org.omg.CSIIOP.DetectMisordering;
import org.omg.CSIIOP.DetectReplay;
import org.omg.CSIIOP.EstablishTrustInClient;
import org.omg.CSIIOP.EstablishTrustInTarget;
import org.omg.CSIIOP.Integrity;
import org.omg.ETF.Connection;
import org.omg.SSLIOP.SSL;
import org.omg.SSLIOP.SSLHelper;
import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;
/**
* @author Andre Spiegel
* @version $Id$
*/
public class IIOPListener
extends org.jacorb.orb.etf.ListenerBase
{
/** the maximum set of security options supported by the SSL mechanism */
private static final int MAX_SSL_OPTIONS = Integrity.value |
Confidentiality.value |
DetectReplay.value |
DetectMisordering.value |
EstablishTrustInTarget.value |
EstablishTrustInClient.value;
/** the minimum set of security options supported by the SSL mechanism
* which cannot be turned off, so they are always supported
*/
private static final int MIN_SSL_OPTIONS = Integrity.value |
DetectReplay.value |
DetectMisordering.value;
private SocketFactoryManager socketFactoryManager = null;
private SSLAcceptor sslAcceptor = null;
private LoopbackAcceptor loopbackAcceptor ;
private boolean supportSSL = false;
private int serverTimeout = 0;
private IIOPAddress address = null;
private IIOPAddress sslAddress = null;
private int target_supports = 0;
private int target_requires = 0;
private boolean generateSSLComponents = true;
public void configure(Configuration configuration)
throws ConfigurationException
{
super.configure(configuration);
socketFactoryManager = orb.getTransportManager().getSocketFactoryManager();
String address_str = configuration.getAttribute("OAAddress",null);
if (address_str != null)
{
ProtocolAddressBase addr = orb.createAddress(address_str);
if (addr instanceof IIOPAddress)
{
address = (IIOPAddress)addr;
}
}
else
{
int oaPort = configuration.getAttributeAsInteger("OAPort",0);
String oaHost = configuration.getAttribute("OAIAddr","");
address = new IIOPAddress(oaHost,oaPort);
}
if (address != null)
{
address.configure (configuration);
}
address_str = configuration.getAttribute("OASSLAddress",null);
if (address_str != null)
{
ProtocolAddressBase addr = orb.createAddress(address_str);
if (addr instanceof IIOPAddress)
{
sslAddress = (IIOPAddress)addr;
}
}
else
{
int sslPort = configuration.getAttributeAsInteger("OASSLPort",0);
String sslHost = configuration.getAttribute("OAIAddr","");
sslAddress = new IIOPAddress(sslHost,sslPort);
}
if (sslAddress != null)
{
sslAddress.configure (configuration);
}
serverTimeout =
configuration.getAttributeAsInteger("jacorb.connection.server.timeout",0);
supportSSL =
configuration.getAttribute("jacorb.security.support_ssl","off").equals("on");
target_supports =
Integer.parseInt(
configuration.getAttribute("jacorb.security.ssl.server.supported_options","20"),
16); // 16 is the base as we take the string value as hex!
// make sure that the minimum options are always in the set of supported options
target_supports |= MIN_SSL_OPTIONS;
target_requires =
Integer.parseInt(
configuration.getAttribute("jacorb.security.ssl.server.required_options","0"),
16);
generateSSLComponents =
configuration.getAttribute("jacorb.security.ssl_components_added_by_ior_interceptor","off").equals("off");
if (!isSSLRequired() ||
configuration.getAttributeAsBoolean("jacorb.security.ssl.always_open_unsecured_address", false))
{
acceptor = new Acceptor("ServerSocketListener");
((Acceptor)acceptor).init();
}
if (supportSSL)
{
sslAcceptor = new SSLAcceptor();
sslAcceptor.init();
}
loopbackAcceptor = new LoopbackAcceptor() ;
profile = createAddressProfile();
}
/**
* {@inheritDoc}
*/
public void listen()
{
super.listen();
if (sslAcceptor != null)
{
sslAcceptor.start();
}
loopbackAcceptor.start() ;
}
/**
* {@inheritDoc}
*/
public void destroy()
{
loopbackAcceptor.terminate() ;
if (sslAcceptor != null)
{
sslAcceptor.terminate();
}
super.destroy();
}
public void renewSSLServerSocket()
{
if (sslAcceptor != null)
{
sslAcceptor.renewSSLServerSocket();
}
}
// internal methods below this line
/**
* Returns true if this Listener should support SSL connections.
*/
private boolean isSSLSupported()
{
return supportSSL;
}
/**
* Returns true if this Listener should <i>require</i> SSL, and not
* offer plain connections.
*/
private boolean isSSLRequired()
{
if (isSSLSupported())
{
// the following is used as a bit mask to check if any SSL
// options are required
return ((target_requires & MAX_SSL_OPTIONS ) != 0);
}
return false;
}
/**
* Creates a new IIOPProfile that describes this transport address.
*/
private IIOPProfile createAddressProfile()
throws ConfigurationException
{
if (acceptor != null)
{
if (address.getPort() == 0)
{
address.setPort(((Acceptor)acceptor).getLocalAddress().getPort());
}
else
{
if (logger.isDebugEnabled())
{
logger.debug ("Using port " + address.getPort());
}
}
}
else if (sslAcceptor == null)
{
throw new org.omg.CORBA.INITIALIZE
("no acceptors found, cannot create address profile");
}
IIOPProfile result = new IIOPProfile(address,null);
if (sslAcceptor != null && generateSSLComponents)
{
result.addComponent (TAG_SSL_SEC_TRANS.value,
createSSL(), SSLHelper.class);
}
result.configure(configuration);
return result;
}
private SSL createSSL()
{
return new SSL
(
(short)target_supports,
(short)target_requires,
(short)sslAcceptor.getLocalAddress().getPort()
);
}
/**
* Creates an ETF connection around a live socket and passes it
* up to the ORB, using either the upcall mechanism or the
* polling mechanism.
*/
private void deliverConnection (Socket socket, boolean isSSL)
{
Connection result = null;
try
{
result = createServerConnection(socket, isSSL);
}
catch (IOException ex)
{
if (logger.isErrorEnabled())
{
logger.error("Could not create connection from socket: ", ex);
}
return;
}
deliverConnection(result);
}
/**
* Template method to create a server-side ETF Connection.
* This can be overridden by subclasses to pass a different
* kind of Connection up to the ORB.
*/
protected Connection createServerConnection (Socket socket,
boolean is_ssl)
throws IOException
{
final TCPConnectionListener tcpListener = socketFactoryManager.getTCPListener();
ServerIIOPConnection result = new ServerIIOPConnection(socket, is_ssl, tcpListener);
if (tcpListener.isListenerEnabled())
{
tcpListener.connectionOpened
(
new TCPConnectionEvent
(
result,
socket.getInetAddress().getHostAddress(),
socket.getPort(),
socket.getLocalPort(),
getLocalhost()
)
);
}
try
{
result.configure(configuration);
}
catch( ConfigurationException ce )
{
throw new org.omg.CORBA.INTERNAL("ConfigurationException: " + ce.toString());
}
return result;
}
// Acceptor classes below this line
private String getLocalhost()
{
String localhost;
try
{
localhost =
InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException uhe)
{
if (logger.isDebugEnabled())
{
logger.debug
("Unable to resolve local host - using default 127.0.0.1");
}
localhost = "127.0.0.1";
}
return localhost;
}
public class Acceptor
extends org.jacorb.orb.etf.ListenerBase.Acceptor
{
private final Object runSync = new Object();
private final boolean keepAlive;
protected ServerSocket serverSocket;
protected boolean terminated = false;
/**
* <code>acceptorExceptionListener</code> is listener to be notified
* of terminal failures by the acceptor.
*/
private final AcceptorExceptionListener acceptorExceptionListener;
/**
* <code>firstPass</code> stores whether we have already done
* one pass in the run method i.e. have accepted one socket.
*/
private boolean firstPass;
protected Acceptor(String name)
{
super();
// initialization deferred to init() method due to JDK bug
setDaemon(true);
setName(name);
keepAlive = configuration.getAttributeAsBoolean("jacorb.connection.server.keepalive", false);
try
{
acceptorExceptionListener = (AcceptorExceptionListener)configuration.getAttributeAsObject("jacorb.acceptor_exception_listener", DefaultAcceptorExceptionListener.class.getName());
}
catch (ConfigurationException e)
{
logger.error("couldn't create a AcceptorExceptionListener", e);
throw new IllegalArgumentException("wrong configuration: " + e);
}
}
public void init()
{
serverSocket = createServerSocket();
if( logger.isDebugEnabled() )
{
logger.debug( "Created socket listener on " +
serverSocket.getInetAddress() + ":" + serverSocket.getLocalPort() );
}
}
/**
* template method that is invoked during the accept loop
* before an incoming connection is accepted.
*/
protected void beginAccept() throws InterruptedException
{
// empty to be overridden
}
/**
* template method that is invoked during the accept loop
* after an incoming connection was processed.
*/
protected void endAccept()
{
// empty to be overridden
}
public final void run()
{
while(true)
{
try
{
synchronized(runSync)
{
if (terminated)
{
if (logger.isInfoEnabled())
{
logger.info( "Listener exited");
}
return;
}
}
beginAccept();
try
{
Socket socket = serverSocket.accept();
setup(socket);
deliverConnection (socket);
firstPass = true;
}
finally
{
endAccept();
}
}
catch (Exception e)
{
synchronized(runSync)
{
handleExceptionInRunLoop(e, terminated);
}
}
}
}
/**
* template method that is invoked when
* an exception occurs during the run loop.
*/
private void handleExceptionInRunLoop(Exception exception, boolean isTerminated)
{
if (!isTerminated)
{
logger.warn("unexpected exception in runloop", exception);
}
acceptorExceptionListener.exceptionCaught
(
new AcceptorExceptionEvent
(
this,
((BasicAdapter) up).getORB(),
exception
)
);
}
protected void doHandleExceptionInRunLoop(Exception exception, boolean isTerminated)
{
// empty to be overridden
}
/**
* Terminates this Acceptor by closing the ServerSocket and interrupting
* the run loop.
*/
public void terminate()
{
synchronized(runSync)
{
terminated = true;
}
try
{
serverSocket.close();
}
catch (java.io.IOException e)
{
if (logger.isWarnEnabled())
{
logger.warn("failed to close ServerSocket", e);
}
}
interrupt();
}
public IIOPAddress getLocalAddress()
{
IIOPAddress addr = new IIOPAddress
(
serverSocket.getInetAddress().toString(),
serverSocket.getLocalPort()
);
if (configuration != null)
{
try
{
addr.configure(configuration);
}
catch( ConfigurationException ce)
{
if (logger.isWarnEnabled())
{
logger.warn("ConfigurationException", ce );
}
}
}
return addr;
}
/**
* Template method that creates the server socket.
*/
protected ServerSocket createServerSocket()
{
try
{
return socketFactoryManager.getServerSocketFactory()
.createServerSocket (address.getPort(),
20,
address.getConfiguredHost());
}
catch (IOException ex)
{
logger.warn("could not create server socket port: " + address.getPort() + " host: " + address.getConfiguredHost(), ex);
throw new org.omg.CORBA.INITIALIZE ("Could not create server socket (" + address.getPort() + "): " + ex.toString());
}
}
/**
* Template method that sets up the socket right after the
* connection has been established. Subclass implementations
* may implement their own logic by overriding doSetup
*/
protected final void setup(Socket socket)
throws IOException
{
socket.setSoTimeout(serverTimeout);
socket.setKeepAlive(keepAlive);
SSLListenerUtil.addListener(orb, socket);
doSetup(socket);
}
protected void doSetup(Socket socket)
{
// empty to be overridden
}
protected void deliverConnection(Socket socket)
{
IIOPListener.this.deliverConnection (socket, false);
}
/**
* <code>getAcceptorSocketLoop</code> returns whether we have done
* a socket accept. This is useful for the AcceptorExceptionListener
* so it can determine for instance if the SSLException has been
* thrown before any connections have been made or after x amount of
* connections - this allows differentiation between initial
* configuration failure and failure to connect to a single client.
*
* @return a <code>boolean</code> value
*/
public boolean getAcceptorSocketLoop()
{
return firstPass;
}
}
private class SSLAcceptor
extends Acceptor
{
private final Object renewSocketSync = new Object();
private boolean renewingSocket;
private boolean blockedOnAccept;
private final int soTimeout;
private SSLAcceptor()
{
super("SSLServerSocketListener");
soTimeout = configuration.getAttributeAsInteger("jacorb.listener.server_socket_timeout", 0);
}
protected ServerSocket createServerSocket()
{
try
{
int port = sslAddress.getPort();
InetAddress configuredHost = sslAddress.getConfiguredHost();
ServerSocket socket = newServerSocket(port, configuredHost);
return socket;
}
catch (IOException e)
{
logger.warn("could not create SSL server socket", e);
throw new org.omg.CORBA.INITIALIZE("Could not create SSL server socket");
}
}
private ServerSocket newServerSocket(int port, InetAddress configuredHost) throws IOException
{
ServerSocket socket = socketFactoryManager.getSSLServerSocketFactory()
.createServerSocket (port,
20,
configuredHost);
if (soTimeout > 0)
{
serverSocket.setSoTimeout(soTimeout);
}
return socket;
}
protected void deliverConnection(Socket socket)
{
IIOPListener.this.deliverConnection (socket, true);
}
protected void beginAccept() throws InterruptedException
{
synchronized (renewSocketSync)
{
//wait 'til new socket has been created
while (renewingSocket)
{
renewSocketSync.wait();
}
//tell renewing thread we're busy
blockedOnAccept = true;
}
}
protected void endAccept()
{
synchronized (renewSocketSync)
{
//tell renewing thread we're done
blockedOnAccept = false;
renewSocketSync.notifyAll();
}
}
protected void doHandleExceptionInRunLoop(Exception exception, boolean isTerminated)
{
// we are only interested in InterruptedExceptions here
if (!(exception instanceof InterruptedException))
{
return;
}
// accept took too long
if (soTimeout > 0)
{
return;
}
// Thread was interrupted
if (isTerminated)
{
return;
}
logger.warn("InterruptedException should only occur if soTimeout > 0", exception);
}
/**
* Replace an existing SSLServersocket by a new one (possibly using
* new key material) that's opened on the same address/port
* combination.
*/
public void renewSSLServerSocket()
{
//remember old settings
int oldPort = serverSocket.getLocalPort();
InetAddress oldAddress = serverSocket.getInetAddress();
try
{
synchronized (renewSocketSync)
{
//tell listener we want to renew. Attention: this needs to
//be done prior to waiting, otherwise we may starve
renewingSocket = true;
//wait 'til listener isn't accepting anymore
while (blockedOnAccept)
{
try
{
renewSocketSync.wait();
}
catch(InterruptedException e)
{
// ignored
}
}
}
try
{
//close old socket
serverSocket.close();
}
catch (Exception e)
{
logger.warn("failed to close SSLServerSocket", e);
}
try
{
//create new ServerSocket with old host/port
serverSocket = newServerSocket(oldPort, oldAddress);
}
catch (Exception e)
{
logger.warn("Failed to create SSLServerSocket", e);
throw new org.omg.CORBA.INITIALIZE(
"Could not create SSL server socket: " + e);
}
}
finally
{
// leave critical section
synchronized(renewSocketSync)
{
//tell listener we're done
renewingSocket = false;
renewSocketSync.notifyAll();
}
}
}
}
private class LoopbackAcceptor implements IIOPLoopback
{
public void start()
{
IIOPLoopbackRegistry.getRegistry().register(getAddress(), this);
}
public void terminate()
{
IIOPLoopbackRegistry.getRegistry().unregister(getAddress());
}
public void initLoopback(final IIOPLoopbackInputStream lis,
final IIOPLoopbackOutputStream los)
{
final IIOPLoopbackConnection connection =
new IIOPLoopbackConnection(lis, los) ;
try
{
connection.configure(configuration);
}
catch( ConfigurationException ce )
{
throw new org.omg.CORBA.INTERNAL("ConfigurationException: " + ce.toString());
}
deliverConnection(connection);
}
private IIOPAddress getAddress()
{
final IIOPProfile profile =
(IIOPProfile)IIOPListener.this.profile;
return (IIOPAddress)profile.getAddress();
}
}
}