package org.jacorb.orb.iiop; /* * JacORB - a free Java ORB * * Copyright (C) 1999-2014 Gerald Brose / The JacORB Team. * * 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 javax.net.ssl.SSLSocket; import org.jacorb.config.Configuration; import org.jacorb.config.ConfigurationException; import org.jacorb.orb.BasicAdapter; import org.jacorb.orb.factory.ServerSocketFactory; 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.CORBA.NO_RESOURCES; 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 */ 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 IIOPListener() { super(); } public void configure(Configuration config) throws ConfigurationException { super.configure(config); socketFactoryManager = orb.getTransportManager().getSocketFactoryManager(); if (listenEndpoint == null) { throw new org.omg.CORBA.INITIALIZE ("listenEndpoint may not be null"); } if (listenEndpoint.getSSLAddress() != null) { sslAddress = (IIOPAddress) listenEndpoint.getSSLAddress(); } if (listenEndpoint.getAddress() != null) { address = (IIOPAddress) listenEndpoint.getAddress(); } if (address != null) { address.configure (configuration); } if (sslAddress != null) { sslAddress.configure (configuration); } serverTimeout = configuration.getAttributeAsInteger("jacorb.connection.server.timeout",0); supportSSL = configuration.getAttributeAsBoolean("jacorb.security.support_ssl", false); target_supports = configuration.getAttributeAsInteger("jacorb.security.ssl.server.supported_options", 0x20, 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 = configuration.getAttributeAsInteger("jacorb.security.ssl.server.required_options", 0, 16); generateSSLComponents = configuration.getAttributeAsBoolean("jacorb.security.ssl_components_added_by_ior_interceptor", true); 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(); } profile = createAddressProfile(); if (configuration.getAttributeAsBoolean("jacorb.iiop.enable_loopback", true)) { loopbackAcceptor = new LoopbackAcceptor(); } } /** * {@inheritDoc} */ public void listen() { super.listen(); if (sslAcceptor != null) { sslAcceptor.start(); } if (loopbackAcceptor != null) { loopbackAcceptor.start(); } } /** * {@inheritDoc} */ public void destroy() { if (loopbackAcceptor != null) { 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 { IIOPProfile result = null; if (acceptor != null) { IIOPAddress serverAddress = ((Acceptor)acceptor).getLocalAddress(); if (address.getPort() == 0) { address.setPort(serverAddress.getPort()); } if (logger.isDebugEnabled()) { logger.debug ("IIOPAddress using port " + address.getPort()); } if (address.getConfiguredHost() == null) { address.setHostInetAddress (serverAddress.getConfiguredHost()); } else { /** * In case some users set the hostname to all zeroes * ("0.0.0.0") which could be a wildcard host if it is not * mapped to a loop-back IP. */ address.setWildcardHost (serverAddress.isWildcard()); } result = new IIOPProfile(serverAddress, null, orb.getGIOPMinorVersion()); result.configure(configuration); // Add all wildcard addresses to the list of alternative addresses if (serverAddress.isWildcard()) { result.addAllWildcardAddresses(configuration); } } else if (sslAcceptor == null) { throw new org.omg.CORBA.INITIALIZE ("no acceptors found, cannot create address profile"); } if (sslAcceptor != null) { IIOPAddress serverAddress = ((SSLAcceptor)sslAcceptor).getLocalAddress(); if (sslAddress.getPort() == 0) { sslAddress.setPort(serverAddress.getPort()); } if (logger.isDebugEnabled()) { logger.debug ("IIOPAddress using ssl port " + sslAddress.getPort()); } if (sslAddress.getConfiguredHost() == null) { sslAddress.setHostInetAddress (serverAddress.getConfiguredHost()); } else { sslAddress.setWildcardHost (serverAddress.isWildcard()); } // This means a non-ssl profile hasn't been created, so create it here. if (result == null) { IIOPAddress tempAddress = ((SSLAcceptor)sslAcceptor).getLocalAddress(); tempAddress.setPort(0); if (address != null && address.getPort() != 0) { tempAddress.setPort(address.getPort()); } result = new IIOPProfile(tempAddress, null, orb.getGIOPMinorVersion()); result.configure(configuration); // Add all wildcard addresses to the list of alternative addresses if (tempAddress.isWildcard()) { result.addAllWildcardAddresses(configuration); } } if (generateSSLComponents) { result.addComponent (TAG_SSL_SEC_TRANS.value, createSSL(), SSLHelper.class); } } 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() { return IIOPAddress.getLocalHostAddress (logger); } /* * Acceptor class */ public class Acceptor extends org.jacorb.orb.etf.ListenerBase.Acceptor { private final Object runSync = new Object(); private final boolean keepAlive; private final IIOPAddress addressToUse; protected final int soTimeout; protected final boolean reuseAddress; protected ServerSocket serverSocket; protected String info = ""; 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, IIOPAddress target) throws ConfigurationException { super(); // initialization deferred to init() method due to JDK bug setDaemon(true); setName(name); keepAlive = configuration.getAttributeAsBoolean("jacorb.connection.server.keepalive", false); soTimeout = configuration.getAttributeAsInteger("jacorb.listener.server_socket_timeout", 0); reuseAddress = configuration.getAttributeAsBoolean("jacorb.connection.server.reuse_address", 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); } addressToUse = target; } protected Acceptor(String name) throws ConfigurationException { this(name, address); } public void init() { serverSocket = createServerSocket(); if( logger.isDebugEnabled() ) { logger.debug( "Created " + info + "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() { try { runLoop(); } finally { if (!terminated) { logger.error(info + "Listener is unexpectedly exiting. the ORB is in an non-functional state!"); } else { logger.debug(info + "Listener exiting"); } } } public final void runLoop() { while(true) { try { synchronized(runSync) { if (terminated) { return; } } beginAccept(); try { Socket socket = serverSocket.accept(); if (terminated) { // JAC#439 // serverSocket.close() and interruption of the // Listener thread seem not to reliable prevent // the serverSocket from accepting further connection requests. // so we might come here although the IIOPListener was already // shutdown. // TODO: prohably it would be better to use // SocketChannel socketChannel = serverSocket.getChannel().accept(); // Socket socket = socketChannel.socket(); // for more reliability if ( ! (socket instanceof SSLSocket) && ! socket.isClosed()) { socket.shutdownOutput(); } socket.close(); if (logger.isInfoEnabled()) { logger.info("closed Socket " + socket + " as " + info + "ServerSocket was closed."); } return; } setup(socket); try { deliverConnection(socket); firstPass = true; } catch(NO_RESOURCES e) { // as no ServerMessageReceptor // was created for this socket // we'll at least close // the socket if ( ! (socket instanceof SSLSocket) && ! socket.isClosed()) { socket.shutdownOutput(); } socket.close(); throw e; } } finally { endAccept(); } } catch (Exception e) { synchronized(runSync) { handleExceptionInRunLoop(e, terminated); } } catch(OutOfMemoryError e) { logger.error("OutOfMemory", e); } } } /** * 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 " + info + "Acceptor runloop", exception); } doHandleExceptionInRunLoop(exception, isTerminated); try { acceptorExceptionListener.exceptionCaught ( new AcceptorExceptionEvent ( this, ((BasicAdapter) up).getORB(), exception ) ); } catch(Exception e) { logger.error("error in Acceptor Exception Listener: " + acceptorExceptionListener + " while handling exception: " + exception, e); } } 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 " + info + "ServerSocket", e); } } interrupt(); } public IIOPAddress getLocalAddress() { IIOPAddress addr = new IIOPAddress ( serverSocket ); if (configuration != null) { try { addr.configure(configuration); } catch( ConfigurationException ce) { if (logger.isWarnEnabled()) { logger.warn("ConfigurationException", ce ); } } } return addr; } protected ServerSocket createServerSocket() { final InetAddress configuredHost = addressToUse.getConfiguredHost(); final int port = addressToUse.getPort(); return createServerSocket(configuredHost, port); } protected ServerSocket createServerSocket(final InetAddress host, final int port) { try { final ServerSocket result; if (host != null) { /* * A server socket is created for a selected host. * In case the port is 0, a port will be assigned * to be used as the listen port. * The backlog is set to -1 so that the system * default value of 50 will be assigned. * */ result = getServerSocketFactory().createServerSocket(port, -1, host); } else { /* * A server socket is created for a wildcard host to listen * for requests on all known network interfaces' host addresses. * In case the port is 0, a port will be assigned * as the listen port. The backlog will be set to * the system default value of 50. * */ result = getServerSocketFactory().createServerSocket(port); } if (soTimeout > 0) { result.setSoTimeout(soTimeout); } if (reuseAddress) { result.setReuseAddress(true); } return result; } catch (IOException ex) { logger.warn("could not create " + info + "ServerSocket port: " + port + " host: " + host, ex); throw new org.omg.CORBA.INITIALIZE ("Could not create " + info + "ServerSocket (" + port + "): " + ex.toString()); } } protected ServerSocketFactory getServerSocketFactory() { return socketFactoryManager.getServerSocketFactory(); } /** * 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); socket.setTcpNoDelay(true); try { SSLListenerUtil.addListener(orb, socket); } catch(Throwable t) { logger.warn("unexpected exception in ssl listener", t); } 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; } } // end of Acceptor private class SSLAcceptor extends Acceptor { private final Object renewSocketSync = new Object(); private boolean renewingSocket; private boolean blockedOnAccept; private SSLAcceptor() throws ConfigurationException { super("SSLServerSocketListener", sslAddress); info = "SSL"; } protected ServerSocketFactory getServerSocketFactory() { return socketFactoryManager.getSSLServerSocketFactory(); } 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 final InetAddress oldAddress = serverSocket.getInetAddress(); final int oldPort = serverSocket.getLocalPort(); 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); } //create new ServerSocket with old host/port serverSocket = createServerSocket(oldAddress, oldPort); } finally { // leave critical section synchronized(renewSocketSync) { //tell listener we're done renewingSocket = false; renewSocketSync.notifyAll(); } } } } private class LoopbackAcceptor implements IIOPLoopback { private final IIOPAddress listenerAddress; private final IIOPAddress loopbackAddress; private final boolean isSSL; public LoopbackAcceptor() { final IIOPProfile iiopProfile = (IIOPProfile)IIOPListener.this.profile; listenerAddress = (IIOPAddress)iiopProfile.getAddress().copy(); loopbackAddress = (IIOPAddress) listenerAddress.copy(); loopbackAddress.setHostname("127.0.0.1"); isSSL = iiopProfile.getSSL() != null; if (isSSL) { listenerAddress.setPort(iiopProfile.getSSLPort()); loopbackAddress.setPort(iiopProfile.getSSLPort()); } if( logger.isDebugEnabled() ) { logger.debug("LoopbackAcceptor creates loopbackAddress using: " + "<" + loopbackAddress.toString() + ">"); } } public void start() { IIOPLoopbackRegistry.getRegistry().register(listenerAddress, this); IIOPLoopbackRegistry.getRegistry().register(loopbackAddress, this); } public void terminate() { IIOPLoopbackRegistry.getRegistry().unregister(listenerAddress); IIOPLoopbackRegistry.getRegistry().unregister(loopbackAddress); } public void initLoopback(final String connectionInfo, final IIOPLoopbackInputStream lis, final IIOPLoopbackOutputStream los) { final IIOPLoopbackConnection connection = new IIOPLoopbackConnection(lis, los) { { connection_info = connectionInfo; } public boolean isSSL() { return isSSL; } }; try { connection.configure(configuration); } catch(ConfigurationException e) { throw new RuntimeException("should never happen", e); } deliverConnection(connection); } } }