View Javadoc

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