View Javadoc

    1   // ========================================================================
    2   // Copyright 2000-2005 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.ByteArrayInputStream;
    18  import java.io.File;
    19  import java.io.IOException;
    20  import java.io.InputStream;
    21  import java.net.InetAddress;
    22  import java.net.ServerSocket;
    23  import java.net.Socket;
    24  import java.net.SocketAddress;
    25  import java.security.KeyStore;
    26  import java.security.SecureRandom;
    27  import java.security.Security;
    28  import java.security.cert.X509Certificate;
    29  import java.util.ArrayList;
    30  import java.util.Arrays;
    31  import java.util.Iterator;
    32  import java.util.List;
    33
    34  import javax.net.ssl.HandshakeCompletedEvent;
    35  import javax.net.ssl.HandshakeCompletedListener;
    36  import javax.net.ssl.KeyManager;
    37  import javax.net.ssl.KeyManagerFactory;
    38  import javax.net.ssl.SSLContext;
    39  import javax.net.ssl.SSLException;
    40  import javax.net.ssl.SSLPeerUnverifiedException;
    41  import javax.net.ssl.SSLServerSocket;
    42  import javax.net.ssl.SSLServerSocketFactory;
    43  import javax.net.ssl.SSLSession;
    44  import javax.net.ssl.SSLSocket;
    45  import javax.net.ssl.TrustManager;
    46  import javax.net.ssl.TrustManagerFactory;
    47
    48  import org.mortbay.io.EndPoint;
    49  import org.mortbay.io.bio.SocketEndPoint;
    50  import org.mortbay.jetty.HttpSchemes;
    51  import org.mortbay.jetty.Request;
    52  import org.mortbay.jetty.bio.SocketConnector;
    53  import org.mortbay.log.Log;
    54  import org.mortbay.resource.Resource;
    55
    56  /* ------------------------------------------------------------ */
    57  /**
    58   * JSSE Socket Listener.
    59   *
    60   * This specialization of HttpListener is an abstract listener that can be used as the basis for a
    61   * specific JSSE listener.
    62   *
    63   * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge
    64   * Research.
    65   *
    66   * @author Greg Wilkins (gregw@mortbay.com)
    67   * @author Court Demas (court@kiwiconsulting.com)
    68   * @author Forge Research Pty Ltd ACN 003 491 576
    69   * @author Jan Hlavat�
    70   */
    71  public class SslSocketConnector extends SocketConnector
    72  {
    73      /**
    74       * The name of the SSLSession attribute that will contain any cached information.
    75       */
    76      static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
    77
    78      /** Default value for the keystore location path. */
    79      public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator
    80              + ".keystore";
    81
    82      /** String name of key password property. */
    83      public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
    84
    85      /** String name of keystore password property. */
    86      public static final String PASSWORD_PROPERTY = "jetty.ssl.password";
    87
    88      /**
    89       * Return the chain of X509 certificates used to negotiate the SSL Session.
    90       * <p>
    91       * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
    92       * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
    93       *
    94       * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
    95       * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
    96       *         connection. <br>
    97       *         Will be null if the chain is missing or empty.
    98       */
    99      private static X509Certificate[] getCertChain(SSLSession sslSession)
    100     {
    101         try
    102         {
    103             javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain();
    104             if (javaxCerts == null || javaxCerts.length == 0)
    105                 return null;
    106
    107             int length = javaxCerts.length;
    108             X509Certificate[] javaCerts = new X509Certificate[length];
    109
    110             java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
    111             for (int i = 0; i < length; i++)
    112             {
    113                 byte bytes[] = javaxCerts[i].getEncoded();
    114                 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
    115                 javaCerts[i] = (X509Certificate) cf.generateCertificate(stream);
    116             }
    117
    118             return javaCerts;
    119         }
    120         catch (SSLPeerUnverifiedException pue)
    121         {
    122             return null;
    123         }
    124         catch (Exception e)
    125         {
    126             Log.warn(Log.EXCEPTION, e);
    127             return null;
    128         }
    129     }
    130
    131
    132     /** Default value for the cipher Suites. */
    133     private String _excludeCipherSuites[] = null;
    134
    135     /** Default value for the keystore location path. */
    136     private String _keystore=DEFAULT_KEYSTORE ;
    137     private String _keystoreType = "JKS"; // type of the key store
    138
    139     /** Set to true if we require client certificate authentication. */
    140     private boolean _needClientAuth = false;
    141     private transient Password _password;
    142     private transient Password _keyPassword;
    143     private transient Password _trustPassword;
    144     private String _protocol= "TLS";
    145     private String _provider;
    146     private String _secureRandomAlgorithm; // cert algorithm
    147     private String _sslKeyManagerFactoryAlgorithm = (Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm
    148     private String _sslTrustManagerFactoryAlgorithm = (Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm
    149
    150     private String _truststore;
    151     private String _truststoreType = "JKS"; // type of the key store
    152
    153     /** Set to true if we would like client certificate authentication. */
    154     private boolean _wantClientAuth = false;
    155     private int _handshakeTimeout = 0; //0 means use maxIdleTime
    156
    157     private boolean _allowRenegotiate =false;
    158
    159     /* ------------------------------------------------------------ */
    160     /**
    161      * Constructor.
    162      */
    163     public SslSocketConnector()
    164     {
    165         super();
    166     }
    167
    168
    169     /* ------------------------------------------------------------ */
    170     /**
    171      * @return True if SSL re-negotiation is allowed (default false)
    172      */
    173     public boolean isAllowRenegotiate()
    174     {
    175         return _allowRenegotiate;
    176     }
    177
    178     /* ------------------------------------------------------------ */
    179     /**
    180      * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
    181      * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
    182      * does not have CVE-2009-3555 fixed, then re-negotiation should
    183      * not be allowed.
    184      * @param allowRenegotiate true if re-negotiation is allowed (default false)
    185      */
    186     public void setAllowRenegotiate(boolean allowRenegotiate)
    187     {
    188         _allowRenegotiate = allowRenegotiate;
    189     }
    190
    191     /* ------------------------------------------------------------ */
    192     public void accept(int acceptorID)
    193         throws IOException, InterruptedException
    194     {
    195         try
    196         {
    197             Socket socket = _serverSocket.accept();
    198             configure(socket);
    199
    200             Connection connection=new SslConnection(socket);
    201             connection.dispatch();
    202         }
    203         catch(SSLException e)
    204         {
    205             Log.warn(e);
    206             try
    207             {
    208                 stop();
    209             }
    210             catch(Exception e2)
    211             {
    212                 Log.warn(e2);
    213                 throw new IllegalStateException(e2.getMessage());
    214             }
    215         }
    216     }
    217
    218     /* ------------------------------------------------------------ */
    219     protected void configure(Socket socket)
    220         throws IOException
    221     {
    222         super.configure(socket);
    223     }
    224
    225     /* ------------------------------------------------------------ */
    226     protected SSLServerSocketFactory createFactory()
    227         throws Exception
    228     {
    229         if (_truststore==null)
    230         {
    231             _truststore=_keystore;
    232             _truststoreType=_keystoreType;
    233         }
    234
    235         KeyManager[] keyManagers = null;
    236         InputStream keystoreInputStream = null;
    237         if (_keystore != null)
    238         	keystoreInputStream = Resource.newResource(_keystore).getInputStream();
    239         KeyStore keyStore = KeyStore.getInstance(_keystoreType);
    240         keyStore.load(keystoreInputStream, _password==null?null:_password.toString().toCharArray());
    241
    242         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);
    243         keyManagerFactory.init(keyStore,_keyPassword==null?null:_keyPassword.toString().toCharArray());
    244         keyManagers = keyManagerFactory.getKeyManagers();
    245
    246         TrustManager[] trustManagers = null;
    247         InputStream truststoreInputStream = null;
    248         if (_truststore != null)
    249         	truststoreInputStream = Resource.newResource(_truststore).getInputStream();
    250         KeyStore trustStore = KeyStore.getInstance(_truststoreType);
    251         trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
    252
    253         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
    254         trustManagerFactory.init(trustStore);
    255         trustManagers = trustManagerFactory.getTrustManagers();
    256
    257
    258         SecureRandom secureRandom = _secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
    259
    260         SSLContext context = _provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol, _provider);
    261
    262         context.init(keyManagers, trustManagers, secureRandom);
    263
    264         return context.getServerSocketFactory();
    265     }
    266
    267     /* ------------------------------------------------------------ */
    268     /**
    269      * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
    270      * This allows the required attributes to be set for SSL requests. <br>
    271      * The requirements of the Servlet specs are:
    272      * <ul>
    273      * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
    274      * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
    275      * <li> an attribute named "javax.servlet.request.X509Certificate" of type
    276      * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
    277      * the order of this array is defined as being in ascending order of trust. The first
    278      * certificate in the chain is the one set by the client, the next is the one used to
    279      * authenticate the first, and so on. </li>
    280      * </ul>
    281      *
    282      * @param endpoint The Socket the request arrived on.
    283      *        This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
    284      * @param request HttpRequest to be customised.
    285      */
    286     public void customize(EndPoint endpoint, Request request)
    287         throws IOException
    288     {
    289         super.customize(endpoint, request);
    290         request.setScheme(HttpSchemes.HTTPS);
    291
    292         SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
    293         SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
    294
    295         try
    296         {
    297             SSLSession sslSession = sslSocket.getSession();
    298             String cipherSuite = sslSession.getCipherSuite();
    299             Integer keySize;
    300             X509Certificate[] certs;
    301
    302             CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR);
    303             if (cachedInfo != null)
    304             {
    305                 keySize = cachedInfo.getKeySize();
    306                 certs = cachedInfo.getCerts();
    307             }
    308             else
    309             {
    310                 keySize = new Integer(ServletSSL.deduceKeyLength(cipherSuite));
    311                 certs = getCertChain(sslSession);
    312                 cachedInfo = new CachedInfo(keySize, certs);
    313                 sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
    314             }
    315
    316             if (certs != null)
    317                 request.setAttribute("javax.servlet.request.X509Certificate", certs);
    318             else if (_needClientAuth) // Sanity check
    319                 throw new IllegalStateException("no client auth");
    320
    321             request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
    322             request.setAttribute("javax.servlet.request.key_size", keySize);
    323         }
    324         catch (Exception e)
    325         {
    326             Log.warn(Log.EXCEPTION, e);
    327         }
    328     }
    329
    330     /* ------------------------------------------------------------ */
    331     public String[] getExcludeCipherSuites() {
    332         return _excludeCipherSuites;
    333     }
    334
    335     /* ------------------------------------------------------------ */
    336     public String getKeystore()
    337     {
    338         return _keystore;
    339     }
    340
    341     /* ------------------------------------------------------------ */
    342     public String getKeystoreType()
    343     {
    344         return (_keystoreType);
    345     }
    346
    347     /* ------------------------------------------------------------ */
    348     public boolean getNeedClientAuth()
    349     {
    350         return _needClientAuth;
    351     }
    352
    353     /* ------------------------------------------------------------ */
    354     public String getProtocol()
    355     {
    356         return _protocol;
    357     }
    358
    359     /* ------------------------------------------------------------ */
    360     public String getProvider() {
    361 	return _provider;
    362     }
    363
    364     /* ------------------------------------------------------------ */
    365     public String getSecureRandomAlgorithm()
    366     {
    367         return (this._secureRandomAlgorithm);
    368     }
    369
    370     /* ------------------------------------------------------------ */
    371     public String getSslKeyManagerFactoryAlgorithm()
    372     {
    373         return (this._sslKeyManagerFactoryAlgorithm);
    374     }
    375
    376     /* ------------------------------------------------------------ */
    377     public String getSslTrustManagerFactoryAlgorithm()
    378     {
    379         return (this._sslTrustManagerFactoryAlgorithm);
    380     }
    381
    382     /* ------------------------------------------------------------ */
    383     public String getTruststore()
    384     {
    385         return _truststore;
    386     }
    387
    388     /* ------------------------------------------------------------ */
    389     public String getTruststoreType()
    390     {
    391         return _truststoreType;
    392     }
    393
    394     /* ------------------------------------------------------------ */
    395     public boolean getWantClientAuth()
    396     {
    397         return _wantClientAuth;
    398     }
    399
    400     /* ------------------------------------------------------------ */
    401     /**
    402      * By default, we're confidential, given we speak SSL. But, if we've been told about an
    403      * confidential port, and said port is not our port, then we're not. This allows separation of
    404      * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
    405      * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
    406      * requiring client certs providing mere INTEGRAL constraints.
    407      */
    408     public boolean isConfidential(Request request)
    409     {
    410         final int confidentialPort = getConfidentialPort();
    411         return confidentialPort == 0 || confidentialPort == request.getServerPort();
    412     }
    413
    414     /* ------------------------------------------------------------ */
    415     /**
    416      * By default, we're integral, given we speak SSL. But, if we've been told about an integral
    417      * port, and said port is not our port, then we're not. This allows separation of listeners
    418      * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
    419      * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
    420      * client certs providing mere INTEGRAL constraints.
    421      */
    422     public boolean isIntegral(Request request)
    423     {
    424         final int integralPort = getIntegralPort();
    425         return integralPort == 0 || integralPort == request.getServerPort();
    426     }
    427
    428     /* ------------------------------------------------------------ */
    429     /**
    430      * @param addr The {@link SocketAddress address} that this server should listen on
    431      * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
    432      * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
    433      * settings as per the current configuration of this connector.
    434      * @see #setWantClientAuth
    435      * @see #setNeedClientAuth
    436      * @see #setCipherSuites
    437      * @exception IOException
    438      */
    439
    440     /* ------------------------------------------------------------ */
    441     protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
    442     {
    443         SSLServerSocketFactory factory = null;
    444         SSLServerSocket socket = null;
    445
    446         try
    447         {
    448             factory = createFactory();
    449
    450             socket = (SSLServerSocket) (host==null?
    451                             factory.createServerSocket(port,backlog):
    452                             factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
    453
    454             if (_wantClientAuth)
    455                 socket.setWantClientAuth(_wantClientAuth);
    456             if (_needClientAuth)
    457                 socket.setNeedClientAuth(_needClientAuth);
    458
    459             if (_excludeCipherSuites != null && _excludeCipherSuites.length >0)
    460             {
    461                 List excludedCSList = Arrays.asList(_excludeCipherSuites);
    462                 String[] enabledCipherSuites = socket.getEnabledCipherSuites();
    463             	List enabledCSList = new ArrayList(Arrays.asList(enabledCipherSuites));
    464             	Iterator exIter = excludedCSList.iterator();
    465
    466                 while (exIter.hasNext())
    467             	{
    468             	    String cipherName = (String)exIter.next();
    469                     if (enabledCSList.contains(cipherName))
    470                     {
    471                         enabledCSList.remove(cipherName);
    472                     }
    473             	}
    474                 enabledCipherSuites = (String[])enabledCSList.toArray(new String[enabledCSList.size()]);
    475
    476                 socket.setEnabledCipherSuites(enabledCipherSuites);
    477             }
    478
    479         }
    480         catch (IOException e)
    481         {
    482             throw e;
    483         }
    484         catch (Exception e)
    485         {
    486             Log.warn(e.toString());
    487             Log.debug(e);
    488             throw new IOException("!JsseListener: " + e);
    489         }
    490         return socket;
    491     }
    492
    493     /* ------------------------------------------------------------ */
    494     /**
    495      * @author Tony Jiang
    496      */
    497     public void setExcludeCipherSuites(String[] cipherSuites) {
    498         this._excludeCipherSuites = cipherSuites;
    499     }
    500
    501     /* ------------------------------------------------------------ */
    502     public void setKeyPassword(String password)
    503     {
    504         _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
    505     }
    506
    507     /* ------------------------------------------------------------ */
    508     /**
    509      * @param keystore The resource path to the keystore, or null for built in keystores.
    510      */
    511     public void setKeystore(String keystore)
    512     {
    513         _keystore = keystore;
    514     }
    515
    516     /* ------------------------------------------------------------ */
    517     public void setKeystoreType(String keystoreType)
    518     {
    519         _keystoreType = keystoreType;
    520     }
    521
    522     /* ------------------------------------------------------------ */
    523     /**
    524      * Set the value of the needClientAuth property
    525      *
    526      * @param needClientAuth true iff we require client certificate authentication.
    527      */
    528     public void setNeedClientAuth(boolean needClientAuth)
    529     {
    530         _needClientAuth = needClientAuth;
    531     }
    532
    533     /* ------------------------------------------------------------ */
    534     public void setPassword(String password)
    535     {
    536         _password = Password.getPassword(PASSWORD_PROPERTY,password,null);
    537     }
    538
    539     /* ------------------------------------------------------------ */
    540     public void setTrustPassword(String password)
    541     {
    542         _trustPassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
    543     }
    544
    545     /* ------------------------------------------------------------ */
    546     public void setProtocol(String protocol)
    547     {
    548         _protocol = protocol;
    549     }
    550
    551     /* ------------------------------------------------------------ */
    552     public void setProvider(String _provider) {
    553 	this._provider = _provider;
    554     }
    555
    556     /* ------------------------------------------------------------ */
    557     public void setSecureRandomAlgorithm(String algorithm)
    558     {
    559         this._secureRandomAlgorithm = algorithm;
    560     }
    561
    562     /* ------------------------------------------------------------ */
    563     public void setSslKeyManagerFactoryAlgorithm(String algorithm)
    564     {
    565         this._sslKeyManagerFactoryAlgorithm = algorithm;
    566     }
    567
    568     /* ------------------------------------------------------------ */
    569     public void setSslTrustManagerFactoryAlgorithm(String algorithm)
    570     {
    571         this._sslTrustManagerFactoryAlgorithm = algorithm;
    572     }
    573
    574
    575     public void setTruststore(String truststore)
    576     {
    577         _truststore = truststore;
    578     }
    579
    580
    581     public void setTruststoreType(String truststoreType)
    582     {
    583         _truststoreType = truststoreType;
    584     }
    585
    586     /* ------------------------------------------------------------ */
    587     /**
    588      * Set the value of the _wantClientAuth property. This property is used when
    589      * {@link #newServerSocket(SocketAddress, int) opening server sockets}.
    590      *
    591      * @param wantClientAuth true iff we want client certificate authentication.
    592      * @see SSLServerSocket#setWantClientAuth
    593      */
    594     public void setWantClientAuth(boolean wantClientAuth)
    595     {
    596         _wantClientAuth = wantClientAuth;
    597     }
    598
    599     /**
    600      * Set the time in milliseconds for so_timeout during ssl handshaking
    601      * @param msec a non-zero value will be used to set so_timeout during
    602      * ssl handshakes. A zero value means the maxIdleTime is used instead.
    603      */
    604     public void setHandshakeTimeout (int msec)
    605     {
    606         _handshakeTimeout = msec;
    607     }
    608
    609
    610     public int getHandshakeTimeout ()
    611     {
    612         return _handshakeTimeout;
    613     }
    614     /**
    615      * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
    616      * and the client certificate chain.
    617      */
    618     private class CachedInfo
    619     {
    620         private X509Certificate[] _certs;
    621         private Integer _keySize;
    622
    623         CachedInfo(Integer keySize, X509Certificate[] certs)
    624         {
    625             this._keySize = keySize;
    626             this._certs = certs;
    627         }
    628
    629         X509Certificate[] getCerts()
    630         {
    631             return _certs;
    632         }
    633
    634         Integer getKeySize()
    635         {
    636             return _keySize;
    637         }
    638     }
    639
    640
    641     public class SslConnection extends Connection
    642     {
    643         public SslConnection(Socket socket) throws IOException
    644         {
    645             super(socket);
    646         }
    647
    648         public void run()
    649         {
    650             try
    651             {
    652                 int handshakeTimeout = getHandshakeTimeout();
    653                 int oldTimeout = _socket.getSoTimeout();
    654                 if (handshakeTimeout > 0)
    655                     _socket.setSoTimeout(handshakeTimeout);
    656
    657                 final SSLSocket ssl=(SSLSocket)_socket;
    658                 ssl.addHandshakeCompletedListener(new HandshakeCompletedListener()
    659                 {
    660                     boolean handshook=false;
    661                     public void handshakeCompleted(HandshakeCompletedEvent event)
    662                     {
    663                         if (handshook)
    664                         {
    665                             if (!_allowRenegotiate)
    666                             {
    667                                 Log.warn("SSL renegotiate denied: "+ssl);
    668                                 try{ssl.close();}catch(IOException e){Log.warn(e);}
    669                             }
    670                         }
    671                         else
    672                             handshook=true;
    673                     }
    674                 });
    675                 ssl.startHandshake();
    676
    677                 if (handshakeTimeout>0)
    678                     _socket.setSoTimeout(oldTimeout);
    679
    680                 super.run();
    681             }
    682             catch (SSLException e)
    683             {
    684                 Log.warn(e);
    685                 try{close();}
    686                 catch(IOException e2){Log.ignore(e2);}
    687             }
    688             catch (IOException e)
    689             {
    690                 Log.debug(e);
    691                 try{close();}
    692                 catch(IOException e2){Log.ignore(e2);}
    693             }
    694         }
    695     }
    696
    697 }