View Javadoc

    1   // ========================================================================
    2   // Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.security;
    16
    17  import java.io.IOException;
    18  import java.nio.ByteBuffer;
    19  import java.nio.channels.SelectionKey;
    20  import java.nio.channels.SocketChannel;
    21
    22  import javax.net.ssl.SSLEngine;
    23  import javax.net.ssl.SSLEngineResult;
    24  import javax.net.ssl.SSLException;
    25  import javax.net.ssl.SSLSession;
    26  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    27
    28  import org.mortbay.io.Buffer;
    29  import org.mortbay.io.Buffers;
    30  import org.mortbay.io.nio.NIOBuffer;
    31  import org.mortbay.io.nio.SelectChannelEndPoint;
    32  import org.mortbay.io.nio.SelectorManager;
    33  import org.mortbay.jetty.EofException;
    34  import org.mortbay.jetty.nio.SelectChannelConnector;
    35  import org.mortbay.log.Log;
    36
    37  /* ------------------------------------------------------------ */
    38  /**
    39   * SslHttpChannelEndPoint.
    40   *
    41   * @author Nik Gonzalez <ngonzalez@exist.com>
    42   * @author Greg Wilkins <gregw@mortbay.com>
    43   */
    44  public class SslHttpChannelEndPoint extends SelectChannelConnector.ConnectorEndPoint implements Runnable
    45  {
    46      private static final ByteBuffer[] __NO_BUFFERS={};
    47
    48      private Buffers _buffers;
    49
    50      private SSLEngine _engine;
    51      private ByteBuffer _inBuffer;
    52      private NIOBuffer _inNIOBuffer;
    53      private ByteBuffer _outBuffer;
    54      private NIOBuffer _outNIOBuffer;
    55
    56      private ByteBuffer[] _gather=new ByteBuffer[2];
    57
    58      private boolean _closing=false;
    59      private SSLEngineResult _result;
    60
    61      private boolean _handshook=false;
    62      private boolean _allowRenegotiate=false;
    63
    64
    65      // ssl
    66      protected SSLSession _session;
    67
    68      /* ------------------------------------------------------------ */
    69      public SslHttpChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
    70              throws SSLException, IOException
    71      {
    72          super(channel,selectSet,key);
    73          _buffers=buffers;
    74
    75          // ssl
    76          _engine=engine;
    77          _session=engine.getSession();
    78
    79          // TODO pool buffers and use only when needed.
    80          _outNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
    81          _outBuffer=_outNIOBuffer.getByteBuffer();
    82          _inNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
    83          _inBuffer=_inNIOBuffer.getByteBuffer();
    84      }
    85
    86
    87      /* ------------------------------------------------------------ */
    88      /**
    89       * @return True if SSL re-negotiation is allowed (default false)
    90       */
    91      public boolean isAllowRenegotiate()
    92      {
    93          return _allowRenegotiate;
    94      }
    95
    96      /* ------------------------------------------------------------ */
    97      /**
    98       * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
    99       * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
    100      * does not have CVE-2009-3555 fixed, then re-negotiation should
    101      * not be allowed.
    102      * @param allowRenegotiate true if re-negotiation is allowed (default false)
    103      */
    104     public void setAllowRenegotiate(boolean allowRenegotiate)
    105     {
    106         _allowRenegotiate = allowRenegotiate;
    107     }
    108
    109     /* ------------------------------------------------------------ */
    110     // TODO get rid of these dumps
    111     public void dump()
    112     {
    113         System.err.println(_result);
    114         // System.err.println(h.toString());
    115         // System.err.println("--");
    116     }
    117
    118     /* ------------------------------------------------------------ */
    119     /* (non-Javadoc)
    120      * @see org.mortbay.io.nio.SelectChannelEndPoint#idleExpired()
    121      */
    122     protected void idleExpired()
    123     {
    124         try
    125         {
    126             _selectSet.getManager().dispatch(new Runnable()
    127             {
    128                 public void run()
    129                 {
    130                     doIdleExpired();
    131                 }
    132             });
    133         }
    134         catch(Exception e)
    135         {
    136             Log.ignore(e);
    137         }
    138     }
    139
    140     /* ------------------------------------------------------------ */
    141     protected void doIdleExpired()
    142     {
    143         super.idleExpired();
    144     }
    145
    146     /* ------------------------------------------------------------ */
    147     public void shutdownOutput() throws IOException
    148     {
    149         close();
    150     }
    151
    152     /* ------------------------------------------------------------ */
    153     public void close() throws IOException
    154     {
    155         // TODO - this really should not be done in a loop here - but with async callbacks.
    156
    157         _closing=true;
    158         long end=System.currentTimeMillis()+((SocketChannel)_channel).socket().getSoTimeout();
    159         try
    160         {
    161             if (isBufferingOutput())
    162             {
    163                 flush();
    164                 while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
    165                 {
    166                     Thread.sleep(100); // TODO non blocking
    167                     flush();
    168                 }
    169             }
    170
    171             _engine.closeOutbound();
    172
    173             loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()) && System.currentTimeMillis()<end)
    174             {
    175                 if (isBufferingOutput())
    176                 {
    177                     flush();
    178                     while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
    179                     {
    180                         Thread.sleep(100); // TODO non blocking
    181                         flush();
    182                     }
    183                 }
    184
    185                 // System.err.println(_channel+" close "+_engine.getHandshakeStatus()+" "+_closing);
    186                 switch(_engine.getHandshakeStatus())
    187                 {
    188                     case FINISHED:
    189                     case NOT_HANDSHAKING:
    190                         _handshook=true;
    191                         break loop;
    192
    193                     case NEED_UNWRAP:
    194                         Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
    195                         try
    196                         {
    197                             ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
    198                             if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
    199                             {
    200                                 break loop;
    201                             }
    202                         }
    203                         catch(SSLException e)
    204                         {
    205                             Log.ignore(e);
    206                         }
    207                         finally
    208                         {
    209                             _buffers.returnBuffer(buffer);
    210                         }
    211                         break;
    212
    213                     case NEED_TASK:
    214                     {
    215                         Runnable task;
    216                         while ((task=_engine.getDelegatedTask())!=null)
    217                         {
    218                             task.run();
    219                         }
    220                         break;
    221                     }
    222
    223                     case NEED_WRAP:
    224                     {
    225                         try
    226                         {
    227                             _outNIOBuffer.compact();
    228                             int put=_outNIOBuffer.putIndex();
    229                             _outBuffer.position(put);
    230                             _result=null;
    231                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
    232                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
    233                         }
    234                         finally
    235                         {
    236                             _outBuffer.position(0);
    237                         }
    238
    239                         break;
    240                     }
    241                 }
    242             }
    243         }
    244         catch(IOException e)
    245         {
    246             Log.ignore(e);
    247         }
    248         catch (InterruptedException e)
    249         {
    250             Log.ignore(e);
    251         }
    252         finally
    253         {
    254             super.close();
    255
    256             if (_inNIOBuffer!=null)
    257                 _buffers.returnBuffer(_inNIOBuffer);
    258             if (_outNIOBuffer!=null)
    259                 _buffers.returnBuffer(_outNIOBuffer);
    260         }
    261     }
    262
    263
    264
    265     /* ------------------------------------------------------------ */
    266     /*
    267      */
    268     public int fill(Buffer buffer) throws IOException
    269     {
    270         ByteBuffer bbuf=extractInputBuffer(buffer);
    271         int size=buffer.length();
    272         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
    273         synchronized (bbuf)
    274         {
    275             try
    276             {
    277                 unwrap(bbuf);
    278
    279                 int wraps=0;
    280                 loop: while (true)
    281                 {
    282                     if (isBufferingOutput())
    283                     {
    284                         flush();
    285                         if (isBufferingOutput())
    286                             break loop;
    287                     }
    288
    289                     // System.err.println(_channel+" fill  "+_engine.getHandshakeStatus()+" "+_closing);
    290                     switch(_engine.getHandshakeStatus())
    291                     {
    292                         case FINISHED:
    293                         case NOT_HANDSHAKING:
    294                             if (_closing)
    295                                 return -1;
    296                             break loop;
    297
    298                         case NEED_UNWRAP:
    299                             checkRenegotiate();
    300                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
    301                             {
    302                                 break loop;
    303                             }
    304                             break;
    305
    306                         case NEED_TASK:
    307                         {
    308                             Runnable task;
    309                             while ((task=_engine.getDelegatedTask())!=null)
    310                             {
    311                                 task.run();
    312                             }
    313
    314                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING &&
    315                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
    316                             {
    317                                 // This should be NEED_WRAP
    318                                 // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS.
    319                                 // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it.
    320                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
    321                                 return -1;
    322                             }
    323                             break;
    324                         }
    325
    326                         case NEED_WRAP:
    327                         {
    328                             checkRenegotiate();
    329                             wraps++;
    330                             synchronized(_outBuffer)
    331                             {
    332                                 try
    333                                 {
    334                                     _outNIOBuffer.compact();
    335                                     int put=_outNIOBuffer.putIndex();
    336                                     _outBuffer.position();
    337                                     _result=null;
    338                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
    339                                     switch(_result.getStatus())
    340                                     {
    341                                         case BUFFER_OVERFLOW:
    342                                         case BUFFER_UNDERFLOW:
    343                                             Log.warn("wrap {}",_result);
    344                                         case CLOSED:
    345                                             _closing=true;
    346                                     }
    347
    348                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
    349                                 }
    350                                 finally
    351                                 {
    352                                     _outBuffer.position(0);
    353                                 }
    354                             }
    355
    356                             flush();
    357
    358                             break;
    359                         }
    360                     }
    361                 }
    362             }
    363             catch(SSLException e)
    364             {
    365                 Log.warn(e.toString());
    366                 Log.debug(e);
    367                 throw e;
    368             }
    369             finally
    370             {
    371                 buffer.setPutIndex(bbuf.position());
    372                 bbuf.position(0);
    373             }
    374
    375             int filled=buffer.length()-size;
    376             if (filled>0)
    377                 _handshook=true;
    378             return filled;
    379         }
    380
    381     }
    382
    383     /* ------------------------------------------------------------ */
    384     public int flush(Buffer buffer) throws IOException
    385     {
    386         return flush(buffer,null,null);
    387     }
    388
    389
    390     /* ------------------------------------------------------------ */
    391     /*
    392      */
    393     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
    394     {
    395         int consumed=0;
    396         int available=header.length();
    397         if (buffer!=null)
    398             available+=buffer.length();
    399
    400         int tries=0;
    401         loop: while (true)
    402         {
    403             if (_outNIOBuffer.length()>0)
    404             {
    405                 flush();
    406                 if (isBufferingOutput())
    407                     break loop;
    408             }
    409
    410             // System.err.println(_channel+" flush "+_engine.getHandshakeStatus()+" "+_closing);
    411             switch(_engine.getHandshakeStatus())
    412             {
    413                 case FINISHED:
    414                 case NOT_HANDSHAKING:
    415                     if (_closing || available==0)
    416                     {
    417                         if (consumed==0)
    418                             consumed= -1;
    419                         break loop;
    420                     }
    421
    422                     int c;
    423                     if (header!=null && header.length()>0)
    424                     {
    425                         if (buffer!=null && buffer.length()>0)
    426                             c=wrap(header,buffer);
    427                         else
    428                             c=wrap(header);
    429                     }
    430                     else
    431                         c=wrap(buffer);
    432
    433                     if (c>0)
    434                     {
    435                         _handshook=true;
    436                         consumed+=c;
    437                         available-=c;
    438                     }
    439                     else if (c<0)
    440                     {
    441                         if (consumed==0)
    442                             consumed=-1;
    443                         break loop;
    444                     }
    445
    446                     break;
    447
    448                 case NEED_UNWRAP:
    449                     checkRenegotiate();
    450                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
    451                     try
    452                     {
    453                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
    454                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
    455                         {
    456                             break loop;
    457                         }
    458                     }
    459                     finally
    460                     {
    461                         _buffers.returnBuffer(buf);
    462                     }
    463
    464                     break;
    465
    466                 case NEED_TASK:
    467                 {
    468                     Runnable task;
    469                     while ((task=_engine.getDelegatedTask())!=null)
    470                     {
    471                         task.run();
    472                     }
    473                     break;
    474                 }
    475
    476                 case NEED_WRAP:
    477                 {
    478                     checkRenegotiate();
    479                     synchronized(_outBuffer)
    480                     {
    481                         try
    482                         {
    483                             _outNIOBuffer.compact();
    484                             int put=_outNIOBuffer.putIndex();
    485                             _outBuffer.position();
    486                             _result=null;
    487                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
    488                             switch(_result.getStatus())
    489                             {
    490                                 case BUFFER_OVERFLOW:
    491                                 case BUFFER_UNDERFLOW:
    492                                     Log.warn("wrap {}",_result);
    493                                 case CLOSED:
    494                                     _closing=true;
    495                             }
    496                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
    497                         }
    498                         finally
    499                         {
    500                             _outBuffer.position(0);
    501                         }
    502                     }
    503
    504                     flush();
    505                     if (isBufferingOutput())
    506                         break loop;
    507
    508                     break;
    509                 }
    510             }
    511         }
    512
    513         return consumed;
    514     }
    515
    516     /* ------------------------------------------------------------ */
    517     public void flush() throws IOException
    518     {
    519         int len=_outNIOBuffer.length();
    520         if (len>0)
    521         {
    522             int flushed=super.flush(_outNIOBuffer);
    523             len=_outNIOBuffer.length();
    524
    525             if (len>0)
    526             {
    527                 Thread.yield();
    528                 flushed=super.flush(_outNIOBuffer);
    529                 len=_outNIOBuffer.length();
    530             }
    531         }
    532     }
    533
    534     /* ------------------------------------------------------------ */
    535     private void checkRenegotiate() throws IOException
    536     {
    537         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
    538         {
    539             Log.warn("SSL renegotiate denied: "+_channel);
    540             close();
    541         }
    542     }
    543
    544     /* ------------------------------------------------------------ */
    545     private ByteBuffer extractInputBuffer(Buffer buffer)
    546     {
    547         assert buffer instanceof NIOBuffer;
    548         NIOBuffer nbuf=(NIOBuffer)buffer;
    549         ByteBuffer bbuf=nbuf.getByteBuffer();
    550         bbuf.position(buffer.putIndex());
    551         return bbuf;
    552     }
    553
    554     /* ------------------------------------------------------------ */
    555     /**
    556      * @return true if progress is made
    557      */
    558     private boolean unwrap(ByteBuffer buffer) throws IOException
    559     {
    560         if (_inNIOBuffer.hasContent())
    561             _inNIOBuffer.compact();
    562         else
    563             _inNIOBuffer.clear();
    564
    565         int total_filled=0;
    566
    567         while (_inNIOBuffer.space()>0 && super.isOpen())
    568         {
    569             try
    570             {
    571                 int filled=super.fill(_inNIOBuffer);
    572                 if (filled<=0)
    573                     break;
    574                 total_filled+=filled;
    575             }
    576             catch(IOException e)
    577             {
    578                 if (_inNIOBuffer.length()==0)
    579                 {
    580                     _outNIOBuffer.clear();
    581                     throw e;
    582                 }
    583                 break;
    584             }
    585         }
    586
    587         if (total_filled==0 && _inNIOBuffer.length()==0)
    588         {
    589             if(!isOpen())
    590             {
    591                 _outNIOBuffer.clear();
    592                 throw new EofException();
    593             }
    594             return false;
    595         }
    596
    597         try
    598         {
    599             _inBuffer.position(_inNIOBuffer.getIndex());
    600             _inBuffer.limit(_inNIOBuffer.putIndex());
    601             _result=null;
    602             _result=_engine.unwrap(_inBuffer,buffer);
    603             _inNIOBuffer.skip(_result.bytesConsumed());
    604         }
    605         finally
    606         {
    607             _inBuffer.position(0);
    608             _inBuffer.limit(_inBuffer.capacity());
    609         }
    610
    611         switch(_result.getStatus())
    612         {
    613             case BUFFER_OVERFLOW:
    614                 throw new IllegalStateException(_result.toString());
    615
    616             case BUFFER_UNDERFLOW:
    617                 if (Log.isDebugEnabled())
    618                     Log.debug("unwrap {}",_result);
    619                 if(!isOpen())
    620                 {
    621                     _inNIOBuffer.clear();
    622                     _outNIOBuffer.clear();
    623                     throw new EofException();
    624                 }
    625                 return (total_filled > 0);
    626
    627             case CLOSED:
    628                 _closing=true;
    629
    630             case OK:
    631                 boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
    632                 return progress;
    633             default:
    634                 Log.warn("unwrap "+_result);
    635                 throw new IOException(_result.toString());
    636         }
    637     }
    638
    639     /* ------------------------------------------------------------ */
    640     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
    641     {
    642         if (buffer.buffer() instanceof NIOBuffer)
    643             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
    644
    645         return ByteBuffer.wrap(buffer.array());
    646     }
    647
    648     /* ------------------------------------------------------------ */
    649     private int wrap(Buffer header, Buffer buffer) throws IOException
    650     {
    651         _gather[0]=extractOutputBuffer(header,0);
    652         synchronized(_gather[0])
    653         {
    654             _gather[0].position(header.getIndex());
    655             _gather[0].limit(header.putIndex());
    656
    657             _gather[1]=extractOutputBuffer(buffer,1);
    658
    659             synchronized(_gather[1])
    660             {
    661                 _gather[1].position(buffer.getIndex());
    662                 _gather[1].limit(buffer.putIndex());
    663
    664                 synchronized(_outBuffer)
    665                 {
    666                     int consumed=0;
    667                     try
    668                     {
    669                         _outNIOBuffer.clear();
    670                         _outBuffer.position(0);
    671                         _outBuffer.limit(_outBuffer.capacity());
    672
    673                         _result=null;
    674                         _result=_engine.wrap(_gather,_outBuffer);
    675                         _outNIOBuffer.setGetIndex(0);
    676                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
    677                         consumed=_result.bytesConsumed();
    678                     }
    679                     finally
    680                     {
    681                         _outBuffer.position(0);
    682
    683                         if (consumed>0 && header!=null)
    684                         {
    685                             int len=consumed<header.length()?consumed:header.length();
    686                             header.skip(len);
    687                             consumed-=len;
    688                             _gather[0].position(0);
    689                             _gather[0].limit(_gather[0].capacity());
    690                         }
    691                         if (consumed>0 && buffer!=null)
    692                         {
    693                             int len=consumed<buffer.length()?consumed:buffer.length();
    694                             buffer.skip(len);
    695                             consumed-=len;
    696                             _gather[1].position(0);
    697                             _gather[1].limit(_gather[1].capacity());
    698                         }
    699                         assert consumed==0;
    700                     }
    701                 }
    702             }
    703         }
    704
    705
    706         switch(_result.getStatus())
    707         {
    708             case BUFFER_OVERFLOW:
    709             case BUFFER_UNDERFLOW:
    710                 Log.warn("wrap {}",_result);
    711
    712             case OK:
    713                 return _result.bytesConsumed();
    714             case CLOSED:
    715                 _closing=true;
    716                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
    717
    718             default:
    719                 Log.warn("wrap "+_result);
    720             throw new IOException(_result.toString());
    721         }
    722     }
    723
    724     /* ------------------------------------------------------------ */
    725     private int wrap(Buffer buffer) throws IOException
    726     {
    727         _gather[0]=extractOutputBuffer(buffer,0);
    728         synchronized(_gather[0])
    729         {
    730             _gather[0].position(buffer.getIndex());
    731             _gather[0].limit(buffer.putIndex());
    732
    733             int consumed=0;
    734             synchronized(_outBuffer)
    735             {
    736                 try
    737                 {
    738                     _outNIOBuffer.clear();
    739                     _outBuffer.position(0);
    740                     _outBuffer.limit(_outBuffer.capacity());
    741                     _result=null;
    742                     _result=_engine.wrap(_gather[0],_outBuffer);
    743                     _outNIOBuffer.setGetIndex(0);
    744                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
    745                     consumed=_result.bytesConsumed();
    746                 }
    747                 finally
    748                 {
    749                     _outBuffer.position(0);
    750
    751                     if (consumed>0 && buffer!=null)
    752                     {
    753                         int len=consumed<buffer.length()?consumed:buffer.length();
    754                         buffer.skip(len);
    755                         consumed-=len;
    756                         _gather[0].position(0);
    757                         _gather[0].limit(_gather[0].capacity());
    758                     }
    759                     assert consumed==0;
    760                 }
    761             }
    762         }
    763         switch(_result.getStatus())
    764         {
    765             case BUFFER_OVERFLOW:
    766             case BUFFER_UNDERFLOW:
    767                 Log.warn("wrap {}",_result);
    768
    769             case OK:
    770                 return _result.bytesConsumed();
    771             case CLOSED:
    772                 _closing=true;
    773                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
    774
    775             default:
    776                 Log.warn("wrap "+_result);
    777             throw new IOException(_result.toString());
    778         }
    779     }
    780
    781     /* ------------------------------------------------------------ */
    782     public boolean isBufferingInput()
    783     {
    784         return _inNIOBuffer.hasContent();
    785     }
    786
    787     /* ------------------------------------------------------------ */
    788     public boolean isBufferingOutput()
    789     {
    790         return _outNIOBuffer.hasContent();
    791     }
    792
    793     /* ------------------------------------------------------------ */
    794     public boolean isBufferred()
    795     {
    796         return true;
    797     }
    798
    799     /* ------------------------------------------------------------ */
    800     public SSLEngine getSSLEngine()
    801     {
    802         return _engine;
    803     }
    804
    805     /* ------------------------------------------------------------ */
    806     public String toString()
    807     {
    808         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
    809     }
    810 }