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