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