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                                 // java sslengine bug on TLS.. this should be NEED_WRAP
301                                 // because a handshake response is needed to be sent to the client                                
302                                 return -1;
303                             }
304                             break;
305                         }
306 
307                         case NEED_WRAP:
308                         {
309                             wraps++;
310                             synchronized(_outBuffer)
311                             {
312                                 try
313                                 {
314                                     _outNIOBuffer.compact();
315                                     int put=_outNIOBuffer.putIndex();
316                                     _outBuffer.position();
317                                     _result=null;
318                                     _last="fill wrap";
319                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
320                                     switch(_result.getStatus())
321                                     {
322                                         case BUFFER_OVERFLOW:
323                                         case BUFFER_UNDERFLOW:
324                                             Log.warn("wrap {}",_result);
325                                         case CLOSED:
326                                             _closing=true;
327                                     }
328                                     
329                                     // h.append("wrap ").append(result).append('\n');
330                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
331                                 }
332                                 finally
333                                 {
334                                     _outBuffer.position(0);
335                                 }
336                             }
337 
338                             flush();
339 
340                             break;
341                         }
342                     }
343                 }
344             }
345             catch(SSLException e)
346             {
347                 Log.warn(e.toString());
348                 Log.debug(e);
349                 throw e;
350             }
351             finally
352             {
353                 buffer.setPutIndex(bbuf.position());
354                 bbuf.position(0);
355             }
356         }
357         return buffer.length()-size; 
358 
359     }
360 
361     /* ------------------------------------------------------------ */
362     public int flush(Buffer buffer) throws IOException
363     {
364         return flush(buffer,null,null);
365     }
366 
367 
368     /* ------------------------------------------------------------ */
369     /*     
370      */
371     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
372     {   
373         int consumed=0;
374         int available=header.length();
375         if (buffer!=null)
376             available+=buffer.length();
377         
378         int tries=0;
379         loop: while (true)
380         {
381             // TODO REMOVE loop check
382             if (tries++>100)
383                 throw new IllegalStateException();
384             
385             // h.append("Flush ").append(tries).append(' ').append(_outNIOBuffer.length()).append('\n');
386             
387             if (_outNIOBuffer.length()>0)
388                 flush();
389 
390             // h.append(_engine.getHandshakeStatus()).append('\n');
391             
392             switch(_engine.getHandshakeStatus())
393             {
394                 case FINISHED:
395                 case NOT_HANDSHAKING:
396 
397                     if (_closing || available==0)
398                     {
399                         if (consumed==0)
400                             consumed= -1;
401                         break loop;
402                     }
403                         
404                     int c=(header!=null && buffer!=null)?wrap(header,buffer):wrap(header);
405                     if (c>0)
406                     {
407                         consumed+=c;
408                         available-=c;
409                     }
410                     else if (c<0)
411                     {
412                         if (consumed==0)
413                             consumed=-1;
414                         break loop;
415                     }
416                     
417                     break;
418 
419                 case NEED_UNWRAP:
420                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
421                     try
422                     {
423                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
424                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
425                         {
426                             // h.append("break").append('\n');
427                             break loop;
428                         }
429                     }
430                     finally
431                     {
432                         _buffers.returnBuffer(buf);
433                     }
434                     
435                     break;
436 
437                 case NEED_TASK:
438                 {
439                     Runnable task;
440                     while ((task=_engine.getDelegatedTask())!=null)
441                     {
442                         // h.append("run task\n");
443                         task.run();
444                     }
445                     break;
446                 }
447 
448                 case NEED_WRAP:
449                 {
450                     synchronized(_outBuffer)
451                     {
452                         try
453                         {
454                             _outNIOBuffer.compact();
455                             int put=_outNIOBuffer.putIndex();
456                             _outBuffer.position();
457                             _result=null;
458                             _last="flush wrap";
459                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
460                             switch(_result.getStatus())
461                             {
462                                 case BUFFER_OVERFLOW:
463                                 case BUFFER_UNDERFLOW:
464                                     Log.warn("wrap {}",_result);
465                                 case CLOSED:
466                                     _closing=true;
467                             }
468                             // h.append("wrap=").append(result).append('\n');
469                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
470                         }
471                         finally
472                         {
473                             _outBuffer.position(0);
474                         }
475                     }
476 
477                     flush();
478 
479                     break;
480                 }
481             }
482         }
483         
484         return consumed;
485     }
486     
487     
488     /* ------------------------------------------------------------ */
489     public void flush() throws IOException
490     {
491         while (_outNIOBuffer.length()>0)
492         {
493             int flushed=super.flush(_outNIOBuffer);
494 
495             // h.append("flushed=").append(flushed).append(" of ").append(_outNIOBuffer.length()).append('\n');
496             if (flushed==0)
497             {
498                 Thread.yield();
499                 flushed=super.flush(_outNIOBuffer);
500                 // h.append("flushed2=").append(flushed).append(" of ").append(_outNIOBuffer.length()).append('\n');
501             }
502         }
503     }
504 
505     /* ------------------------------------------------------------ */
506     private ByteBuffer extractInputBuffer(Buffer buffer)
507     {
508         assert buffer instanceof NIOBuffer;
509         NIOBuffer nbuf=(NIOBuffer)buffer;
510         ByteBuffer bbuf=nbuf.getByteBuffer();
511         bbuf.position(buffer.putIndex());
512         return bbuf;
513     }
514 
515     /* ------------------------------------------------------------ */
516     /**
517      * @return true if progress is made
518      */
519     private boolean unwrap(ByteBuffer buffer) throws IOException
520     {
521         if (_inNIOBuffer.hasContent())
522             _inNIOBuffer.compact();
523         else 
524             _inNIOBuffer.clear();
525 
526         int total_filled=0;
527         while (_inNIOBuffer.space()>0 && super.isOpen())
528         {
529             try
530             {
531                 int filled=super.fill(_inNIOBuffer);
532                 // h.append("fill=").append(filled).append('\n');
533                 if (filled<=0)
534                     break;
535                 total_filled+=filled;
536             }
537             catch(IOException e)
538             {
539                 if (_inNIOBuffer.length()==0)
540                     throw e;
541                 break;
542             }
543         }
544 
545         // h.append("inNIOBuffer=").append(_inNIOBuffer.length()).append('\n');
546         
547         if (_inNIOBuffer.length()==0)
548         {
549             if(!isOpen())
550                 throw new org.mortbay.jetty.EofException();
551             return false;
552         }
553 
554         try
555         {
556             _inBuffer.position(_inNIOBuffer.getIndex());
557             _inBuffer.limit(_inNIOBuffer.putIndex());
558             _result=null;
559             _last="unwrap";
560             _result=_engine.unwrap(_inBuffer,buffer);
561             // h.append("unwrap=").append(result).append('\n');
562             _inNIOBuffer.skip(_result.bytesConsumed());
563         }
564         finally
565         {
566             _inBuffer.position(0);
567             _inBuffer.limit(_inBuffer.capacity());
568         }
569         
570 
571         switch(_result.getStatus())
572         {
573             case BUFFER_OVERFLOW:
574             case BUFFER_UNDERFLOW:
575                 if (Log.isDebugEnabled()) Log.debug("unwrap {}",_result);
576                 return (total_filled > 0);
577                 
578             case CLOSED:
579                 _closing=true;
580             case OK:
581                 boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;    
582                 // h.append("progress=").append(progress).append('\n');
583                 return progress;
584             default:
585                 Log.warn("unwrap "+_result);
586             throw new IOException(_result.toString());
587         }
588     }
589 
590     
591     /* ------------------------------------------------------------ */
592     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
593     {
594         NIOBuffer nBuf=null;
595 
596         if (buffer.buffer() instanceof NIOBuffer)
597         {
598             nBuf=(NIOBuffer)buffer.buffer();
599             return nBuf.getByteBuffer();
600         }
601         else
602         {
603             if (_reuseBuffer[n]==null)
604                 _reuseBuffer[n] = (NIOBuffer)_buffers.getBuffer(_session.getApplicationBufferSize());
605             NIOBuffer buf = _reuseBuffer[n];
606             buf.clear();
607             buf.put(buffer);
608             return buf.getByteBuffer();
609         }
610     }
611 
612     /* ------------------------------------------------------------ */
613     private int wrap(Buffer header, Buffer buffer) throws IOException
614     {
615         _gather[0]=extractOutputBuffer(header,0);
616         synchronized(_gather[0])
617         {
618             _gather[0].position(header.getIndex());
619             _gather[0].limit(header.putIndex());
620 
621             _gather[1]=extractOutputBuffer(buffer,1);
622 
623             synchronized(_gather[1])
624             {
625                 _gather[1].position(buffer.getIndex());
626                 _gather[1].limit(buffer.putIndex());
627 
628                 synchronized(_outBuffer)
629                 {
630                     int consumed=0;
631                     try
632                     {
633                         _outNIOBuffer.clear();
634                         _outBuffer.position(0);
635                         _outBuffer.limit(_outBuffer.capacity());
636 
637                         _result=null;
638                         _last="wrap wrap";
639                         _result=_engine.wrap(_gather,_outBuffer);
640                         // h.append("wrap2=").append(result).append('\n');
641                         _outNIOBuffer.setGetIndex(0);
642                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
643                         consumed=_result.bytesConsumed();
644                     }
645                     finally
646                     {
647                         _outBuffer.position(0);
648 
649                         if (consumed>0 && header!=null)
650                         {
651                             int len=consumed<header.length()?consumed:header.length();
652                             header.skip(len);
653                             consumed-=len;
654                             _gather[0].position(0);
655                             _gather[0].limit(_gather[0].capacity());
656                         }
657                         if (consumed>0 && buffer!=null)
658                         {
659                             int len=consumed<buffer.length()?consumed:buffer.length();
660                             buffer.skip(len);
661                             consumed-=len;
662                             _gather[1].position(0);
663                             _gather[1].limit(_gather[1].capacity());
664                         }
665                         assert consumed==0;
666                     }
667                 }
668             }
669         }
670         
671 
672         switch(_result.getStatus())
673         {
674             case BUFFER_OVERFLOW:
675             case BUFFER_UNDERFLOW:
676                 Log.warn("wrap {}",_result);
677                 
678             case OK:
679                 return _result.bytesConsumed();
680             case CLOSED:
681                 _closing=true;
682                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
683 
684             default:
685                 Log.warn("wrap "+_result);
686             throw new IOException(_result.toString());
687         }
688     }
689 
690     /* ------------------------------------------------------------ */
691     private int wrap(Buffer header) throws IOException
692     {
693         _gather[0]=extractOutputBuffer(header,0);
694         synchronized(_gather[0])
695         {
696             _gather[0].position(header.getIndex());
697             _gather[0].limit(header.putIndex());
698 
699             int consumed=0;
700             synchronized(_outBuffer)
701             {
702                 try
703                 {
704                     _outNIOBuffer.clear();
705                     _outBuffer.position(0);
706                     _outBuffer.limit(_outBuffer.capacity());
707                     _result=null;
708                     _last="wrap wrap";
709                     _result=_engine.wrap(_gather[0],_outBuffer);
710                     // h.append("wrap1=").append(result).append('\n');
711                     _outNIOBuffer.setGetIndex(0);
712                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
713                     consumed=_result.bytesConsumed();
714                 }
715                 finally
716                 {
717                     _outBuffer.position(0);
718 
719                     if (consumed>0 && header!=null)
720                     {
721                         int len=consumed<header.length()?consumed:header.length();
722                         header.skip(len);
723                         consumed-=len;
724                         _gather[0].position(0);
725                         _gather[0].limit(_gather[0].capacity());
726                     }
727                     assert consumed==0;
728                 }
729             }
730         }
731         switch(_result.getStatus())
732         {
733             case BUFFER_OVERFLOW:
734             case BUFFER_UNDERFLOW:
735                 Log.warn("wrap {}",_result);
736                 
737             case OK:
738                 return _result.bytesConsumed();
739             case CLOSED:
740                 _closing=true;
741                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
742 
743             default:
744                 Log.warn("wrap "+_result);
745             throw new IOException(_result.toString());
746         }
747     }
748 
749     /* ------------------------------------------------------------ */
750     public boolean isBufferingInput()
751     {
752         return _inNIOBuffer.hasContent();
753     }
754 
755     /* ------------------------------------------------------------ */
756     public boolean isBufferingOutput()
757     {
758         return _outNIOBuffer.hasContent();
759     }
760 
761     /* ------------------------------------------------------------ */
762     public boolean isBufferred()
763     {
764         return true;
765     }
766 
767     /* ------------------------------------------------------------ */
768     public SSLEngine getSSLEngine()
769     {
770         return _engine;
771     }
772 
773     /* ------------------------------------------------------------ */
774     public String toString()
775     {
776         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" last "+_last+" "+_result;
777     }
778 }