Logo Search packages:      
Sourcecode: zeroc-ice-java version File versions  Download package

TransceiverI.java

// **********************************************************************
//
// Copyright (c) 2003-2006 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

package IceSSL;

import java.nio.*;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;

final class TransceiverI implements IceInternal.Transceiver
{
    public java.nio.channels.SelectableChannel
    fd()
    {
      assert(_fd != null);
      return _fd;
    }

    public void
    close()
    {
      if(_instance.networkTraceLevel() >= 1)
      {
          String s = "closing ssl connection\n" + toString();
          _logger.trace(_instance.networkTraceCategory(), s);
      }

      assert(_fd != null);

      if(_readSelector != null)
      {
          try
          {
            _readSelector.close();
          }
          catch(java.io.IOException ex)
          {
            // Ignore.
          }
          _readSelector = null;
      }

      if(_writeSelector != null)
      {
          try
          {
            _writeSelector.close();
          }
          catch(java.io.IOException ex)
          {
            // Ignore.
          }
          _writeSelector = null;
      }

      try
      {
          _engine.closeInbound();
      }
      catch(SSLException ex)
      {
          //
          // SSLEngine always raises an exception with this message:
          //
          // Inbound closed before receiving peer's close_notify: possible truncation attack?
          //
          // We would probably need to wait for a response in shutdown() to avoid this.
          // For now, we'll ignore this exception.
          //
          //_logger.error("IceSSL: error during close\n" + ex.getMessage());
      }

      try
      {
          _fd.close();
      }
      catch(java.io.IOException ex)
      {
          Ice.SocketException se = new Ice.SocketException();
          se.initCause(ex);
          throw se;
      }
      finally
      {
          _fd = null;
      }
    }

    //
    // All methods that can write to the socket are synchronized.
    //
    public synchronized void
    shutdownWrite()
    {
      if(_instance.networkTraceLevel() >= 2)
      {
          String s = "shutting down ssl connection for writing\n" + toString();
          _logger.trace(_instance.networkTraceCategory(), s);
      }

      shutdown();

      assert(_fd != null);
      java.net.Socket socket = _fd.socket();
      try
      {
          socket.shutdownOutput(); // Shutdown socket for writing.
      }
      catch(java.net.SocketException ex)
      {
          //
          // Ignore errors indicating that we are shutdown already.
          //
          if(!IceInternal.Network.notConnected(ex))
          {
            Ice.SocketException se = new Ice.SocketException();
            se.initCause(ex);
            throw se;
          }
      }
      catch(java.io.IOException ex)
      {
          Ice.SocketException se = new Ice.SocketException();
          se.initCause(ex);
          throw se;
      }
    }

    //
    // All methods that can write to the socket are synchronized.
    //
    public synchronized void
    shutdownReadWrite()
    {
      if(_instance.networkTraceLevel() >= 2)
      {
          String s = "shutting down ssl connection for reading and writing\n" + toString();
          _logger.trace(_instance.networkTraceCategory(), s);
      }

      shutdown();

      assert(_fd != null);
      java.net.Socket socket = _fd.socket();
      try
      {
          socket.shutdownInput(); // Shutdown socket for reading
          socket.shutdownOutput(); // Shutdown socket for writing
      }
      catch(java.net.SocketException ex)
      {
          // Ignore.
      }
      catch(java.io.IOException ex)
      {
          Ice.SocketException se = new Ice.SocketException();
          se.initCause(ex);
          throw se;
      }
    }

    //
    // All methods that can write to the socket are synchronized.
    //
    public synchronized void
    write(IceInternal.BasicStream stream, int timeout)
      throws IceInternal.LocalExceptionWrapper
    {
      //
      // Complete handshaking first if necessary.
      //
      if(!_handshakeComplete)
      {
          handshake(timeout);
      }

      ByteBuffer buf = stream.prepareWrite();

      //
      // Write any pending data to the socket.
      //
      flush(timeout);

      try
      {
          while(buf.hasRemaining())
          {
            final int rem = buf.remaining();

            //
            // Encrypt the buffer.
            //
            SSLEngineResult result = _engine.wrap(buf, _netOutput);
            switch(result.getStatus())
            {
            case BUFFER_OVERFLOW:
                assert(false);
                break;
            case BUFFER_UNDERFLOW:
                assert(false);
                break;
            case CLOSED:
                throw new Ice.ConnectionLostException();
            case OK:
                break;
            }

            //
            // Write the encrypted data to the socket.
            //
            if(result.bytesProduced() > 0)
            {
                flush(timeout);

                if(_instance.networkTraceLevel() >= 3)
                {
                  String s = "sent " + result.bytesConsumed() + " of " + rem + " bytes via ssl\n" + toString();
                  _logger.trace(_instance.networkTraceCategory(), s);
                }

                if(_stats != null)
                {
                  _stats.bytesSent(type(), result.bytesConsumed());
                }
            }
          }
      }
      catch(SSLException ex)
      {
          Ice.SecurityException e = new Ice.SecurityException();
          e.reason = "IceSSL: error while encoding message";
          e.initCause(ex);
          throw e;
      }
    }

    public boolean
    read(IceInternal.BasicStream stream, int timeout)
    {
      ByteBuffer buf = stream.prepareRead();

      int rem = 0;
      if(_instance.networkTraceLevel() >= 3)
      {
          rem = buf.remaining();
      }

      //
      // Complete handshaking first if necessary.
      //
      if(!_handshakeComplete)
      {
          handshake(timeout);
      }

      //
      // Try to satisfy the request from data we've already decrypted.
      //
      int pos = buf.position();
      fill(buf);

      if(_instance.networkTraceLevel() >= 3 && buf.position() > pos)
      {
          String s = "received " + (buf.position() - pos) + " of " + rem + " bytes via ssl\n" + toString();
          _logger.trace(_instance.networkTraceCategory(), s);
      }

      if(_stats != null && buf.position() > pos)
      {
          _stats.bytesReceived(type(), buf.position() - pos);
      }

      //
      // Read and decrypt more data if necessary. Note that we might read
      // more data from the socket than is actually necessary to fill the
      // caller's stream.
      //
      try
      {
          while(buf.hasRemaining())
          {
            _netInput.flip();
            SSLEngineResult result = _engine.unwrap(_netInput, _appInput);
            _netInput.compact();
            switch(result.getStatus())
            {
            case BUFFER_OVERFLOW:
                assert(false);
                break;
            case BUFFER_UNDERFLOW:
                read(timeout);
                continue;
            case CLOSED:
                throw new Ice.ConnectionLostException();
            case OK:
                break;
            }

            pos = buf.position();
            fill(buf);

            if(_instance.networkTraceLevel() >= 3 && buf.position() > pos)
            {
                String s = "received " + (buf.position() - pos) + " of " + rem + " bytes via ssl\n" + toString();
                _logger.trace(_instance.networkTraceCategory(), s);
            }

            if(_stats != null && buf.position() > pos)
            {
                _stats.bytesReceived(type(), buf.position() - pos);
            }
          }
      }
      catch(SSLException ex)
      {
          Ice.SecurityException e = new Ice.SecurityException();
          e.reason = "IceSSL: error during read";
          e.initCause(ex);
          throw e;
      }

      //
      // Return a boolean to indicate whether more data is available.
      //
      return _netInput.position() > 0;
    }

    public String
    type()
    {
      return "ssl";
    }

    public String
    toString()
    {
      return _desc;
    }

    ConnectionInfo
    getConnectionInfo()
    {
      //
      // This can only be called on an open transceiver.
      //
      assert(_fd != null);
      return _info;
    }

    //
    // Only for use by ConnectorI, AcceptorI.
    //
    TransceiverI(Instance instance, javax.net.ssl.SSLEngine engine, java.nio.channels.SocketChannel fd,
             String host, boolean incoming, String adapterName)
    {
      _instance = instance;
      _engine = engine;
      _fd = fd;
      _host = host;
      _adapterName = adapterName;
      _incoming = incoming;
      _logger = instance.communicator().getLogger();
      try
      {
          _stats = instance.communicator().getStats();
      }
      catch(Ice.CommunicatorDestroyedException ex)
      {
          // Ignore.
      }
      _desc = IceInternal.Network.fdToString(_fd);
      // TODO: Buffer cache?
      _appInput = ByteBuffer.allocateDirect(engine.getSession().getApplicationBufferSize() * 2);
      _netInput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2);
      _netOutput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2);
      _handshakeComplete = false;
    }

    protected void
    finalize()
      throws Throwable
    {
      IceUtil.Assert.FinalizerAssert(_fd == null);

      super.finalize();
    }

    private void
    flush(int timeout)
    {
      _netOutput.flip();
      while(_netOutput.hasRemaining())
      {
          try
          {
            assert(_fd != null);
            int ret = _fd.write(_netOutput);

            if(ret == -1)
            {
                throw new Ice.ConnectionLostException();
            }

            if(ret == 0)
            {
                if(timeout == 0)
                {
                  throw new Ice.TimeoutException();
                }

                if(_writeSelector == null)
                {
                  _writeSelector = java.nio.channels.Selector.open();
                  _fd.register(_writeSelector, java.nio.channels.SelectionKey.OP_WRITE, null);
                }

                try
                {
                  if(timeout > 0)
                  {
                      long start = System.currentTimeMillis();
                      int n = _writeSelector.select(timeout);
                      if(n == 0 && System.currentTimeMillis() >= start + timeout)
                      {
                        throw new Ice.TimeoutException();
                      }
                  }
                  else
                  {
                      _writeSelector.select();
                  }
                }
                catch(java.io.InterruptedIOException ex)
                {
                  // Ignore.
                }

                continue;
            }
          }
          catch(java.io.InterruptedIOException ex)
          {
            continue;
          }
          catch(java.io.IOException ex)
          {
            if(IceInternal.Network.connectionLost(ex))
            {
                Ice.ConnectionLostException se = new Ice.ConnectionLostException();
                se.initCause(ex);
                throw se;
            }

            Ice.SocketException se = new Ice.SocketException();
            se.initCause(ex);
            throw se;
          }
      }
      _netOutput.clear();
    }

    private void
    read(int timeout)
    {
      while(true)
      {
          try
          {
            assert(_fd != null);
            int ret = _fd.read(_netInput);

            if(ret == -1)
            {
                throw new Ice.ConnectionLostException();
            }

            if(ret == 0)
            {
                if(timeout == 0)
                {
                  throw new Ice.TimeoutException();
                }

                if(_readSelector == null)
                {
                  _readSelector = java.nio.channels.Selector.open();
                  _fd.register(_readSelector, java.nio.channels.SelectionKey.OP_READ, null);
                }

                try
                {
                  if(timeout > 0)
                  {
                      long start = System.currentTimeMillis();
                      int n = _readSelector.select(timeout);
                      if(n == 0 && System.currentTimeMillis() >= start + timeout)
                      {
                        throw new Ice.TimeoutException();
                      }
                  }
                  else
                  {
                      _readSelector.select();
                  }
                }
                catch(java.io.InterruptedIOException ex)
                {
                  // Ignore.
                }

                continue;
            }

            break;
          }
          catch(java.io.InterruptedIOException ex)
          {
            continue;
          }
          catch(java.io.IOException ex)
          {
            if(IceInternal.Network.connectionLost(ex))
            {
                Ice.ConnectionLostException se = new Ice.ConnectionLostException();
                se.initCause(ex);
                throw se;
            }

            Ice.SocketException se = new Ice.SocketException();
            se.initCause(ex);
            throw se;
          }
      }
    }

    private void
    handshake(int timeout)
    {
      try
      {
          HandshakeStatus status = _engine.getHandshakeStatus();
          while(!_handshakeComplete)
          {
            SSLEngineResult result = null;
            switch(status)
            {
            case FINISHED:
                handshakeCompleted();
                break;
            case NEED_TASK:
            {
                // TODO: Use separate threads & join with timeout?
                Runnable task;
                while((task = _engine.getDelegatedTask()) != null)
                {
                  task.run();
                }
                status = _engine.getHandshakeStatus();
                break;
            }
            case NEED_UNWRAP:
            {
                //
                // The engine needs to receive a message. If we don't have any
                // data left in _netInput, then read some more.
                //
                _netInput.flip();
                if(!_netInput.hasRemaining())
                {
                  _netInput.clear();
                  read(timeout);
                  _netInput.flip();
                }
                result = _engine.unwrap(_netInput, _appInput);
                _netInput.compact();
                //
                // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult().
                //
                status = result.getHandshakeStatus();
                break;
            }
            case NEED_WRAP:
            {
                //
                // The engine needs to send a message.
                //
                result = _engine.wrap(_emptyBuffer, _netOutput);
                if(result.bytesProduced() > 0)
                {
                  flush(timeout);
                }
                //
                // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult().
                //
                status = result.getHandshakeStatus();
                break;
            }
            case NOT_HANDSHAKING:
                assert(false);
                break;
            }

            if(result != null)
            {
                switch(result.getStatus())
                {
                case BUFFER_OVERFLOW:
                  assert(false);
                  break;
                case BUFFER_UNDERFLOW:
                  // Need to read again.
                  assert(status == HandshakeStatus.NEED_UNWRAP);
                  break;
                case CLOSED:
                  throw new Ice.ConnectionLostException();
                case OK:
                  break;
                }
            }
          }
      }
      catch(SSLException ex)
      {
          Ice.SecurityException e = new Ice.SecurityException();
          e.reason = "IceSSL: handshake error";
          e.initCause(ex);
          throw e;
      }
    }

    private void
    fill(ByteBuffer buf)
    {
      _appInput.flip();
      if(_appInput.hasRemaining())
      {
          int bytesAvailable = _appInput.remaining();
          int bytesNeeded = buf.remaining();
          if(bytesAvailable > bytesNeeded)
          {
            bytesAvailable = bytesNeeded;
          }
          if(buf.hasArray())
          {
            //
            // Copy directly into the destination buffer's backing array.
            //
            byte[] arr = buf.array();
            int offset = buf.arrayOffset() + buf.position();
            _appInput.get(arr, offset, bytesAvailable);
            buf.position(offset + bytesAvailable);
          }
          else if(_appInput.hasArray())
          {
            //
            // Copy directly from the source buffer's backing array.
            //
            byte[] arr = _appInput.array();
            int offset = _appInput.arrayOffset() + _appInput.position();
            buf.put(arr, offset, bytesAvailable);
            _appInput.position(offset + bytesAvailable);
          }
          else
          {
            //
            // Copy using a temporary array.
            //
            byte[] arr = new byte[bytesAvailable];
            _appInput.get(arr);
            buf.put(arr);
          }
      }
      _appInput.compact();
    }

    private void
    shutdown()
    {
      //
      // Send the close_notify message.
      //
      _engine.closeOutbound();
      try
      {
          _netOutput.clear();
          while(!_engine.isOutboundDone())
          {
            _engine.wrap(_emptyBuffer, _netOutput);
            try
            {
                flush(-1);
            }
            catch(Ice.ConnectionLostException ex)
            {
                // Ignore.
            }
          }
      }
      catch(SSLException ex)
      {
          Ice.SecurityException se = new Ice.SecurityException();
          se.reason = "IceSSL: SSL failure while shutting down socket";
          se.initCause(ex);
          throw se;
      }
    }

    private void
    handshakeCompleted()
    {
      _handshakeComplete = true;

      //
      // IceSSL.VerifyPeer is translated into the proper SSLEngine configuration
      // for a server, but we have to do it ourselves for a client.
      //
      if(!_incoming)
      {
          int verifyPeer =
            _instance.communicator().getProperties().getPropertyAsIntWithDefault("IceSSL.VerifyPeer", 2);
          if(verifyPeer > 0)
          {
            try
            {
                _engine.getSession().getPeerCertificates();
            }
            catch(javax.net.ssl.SSLPeerUnverifiedException ex)
            {
                Ice.SecurityException e = new Ice.SecurityException();
                e.reason = "IceSSL: server did not supply a certificate";
                e.initCause(ex);
                throw e;
            }
          }
      }

      //
      // Additional verification.
      //
      _info = Util.populateConnectionInfo(_engine.getSession(), _fd.socket(), _adapterName, _incoming);
      _instance.verifyPeer(_info, _fd, _host, _incoming);

      if(_instance.networkTraceLevel() >= 1)
      {
          String s;
          if(_incoming)
          {
            s = "accepted ssl connection\n" + IceInternal.Network.fdToString(_fd);
          }
          else
          {
            s = "ssl connection established\n" + IceInternal.Network.fdToString(_fd);
          }
          _logger.trace(_instance.networkTraceCategory(), s);
      }

      if(_instance.securityTraceLevel() >= 1)
      {
          _instance.traceConnection(_fd, _engine, _incoming);
      }
    }

    private Instance _instance;
    private java.nio.channels.SocketChannel _fd;
    private javax.net.ssl.SSLEngine _engine;
    private String _host;
    private String _adapterName;
    private boolean _incoming;
    private Ice.Logger _logger;
    private Ice.Stats _stats;
    private String _desc;
    private ByteBuffer _appInput; // Holds clear-text data to be read by the application.
    private ByteBuffer _netInput; // Holds encrypted data read from the socket.
    private ByteBuffer _netOutput; // Holds encrypted data to be written to the socket.
    private static ByteBuffer _emptyBuffer = ByteBuffer.allocate(0); // Used during handshaking.
    private boolean _handshakeComplete;
    private java.nio.channels.Selector _readSelector;
    private java.nio.channels.Selector _writeSelector;
    private ConnectionInfo _info;
}

Generated by  Doxygen 1.6.0   Back to index