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