*/ private void closeInternal(boolean selfInitiated) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", called closeInternal(" + selfInitiated + ")"); } int state = getConnectionState(); boolean closeSocketCalled = false; Throwable cachedThrowable = null; try { switch (state) { /* * java.net code sometimes closes sockets "early", when * we can't actually do I/O on them. */ case cs_START: break; /* * If we're closing down due to error, we already sent (or else * received) the fatal alert ... no niceties, blow the connection * away as quickly as possible (even if we didn't allocate the * socket ourselves; it's unusable, regardless). */ case cs_ERROR: closeSocket(); break; /* * Sometimes close() gets called more than once. */ case cs_CLOSED: case cs_APP_CLOSED: break; /* * Otherwise we indicate clean termination. */ // case cs_HANDSHAKE: // case cs_DATA: // case cs_RENEGOTIATE: // case cs_SENT_CLOSE: default: synchronized (this) { if (((state = getConnectionState()) == cs_CLOSED) || (state == cs_ERROR) || (state == cs_APP_CLOSED)) { return; // connection was closed while we waited } if (state != cs_SENT_CLOSE) { try { warning(Alerts.alert_close_notify); connectionState = cs_SENT_CLOSE; } catch (Throwable th) { // we need to ensure socket is closed out // if we encounter any errors. connectionState = cs_ERROR; // cache this for later use cachedThrowable = th; closeSocketCalled = true; closeSocket(selfInitiated); } } } // If state was cs_SENT_CLOSE before, we don't do the actual // closing since it is already in progress. if (state == cs_SENT_CLOSE) { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", close invoked again; state = " + getConnectionState()); } if (selfInitiated == false) { // We were called because a close_notify message was // received. This may be due to another thread calling // read() or due to our call to waitForClose() below. // In either case, just return. return; } // Another thread explicitly called close(). We need to // wait for the closing to complete before returning. synchronized (this) { while (connectionState < cs_CLOSED) { try { this.wait(); } catch (InterruptedException e) { // ignore } } } if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", after primary close; state = " + getConnectionState()); } return; } if (!closeSocketCalled) { closeSocketCalled = true; closeSocket(selfInitiated); } break; } } finally { synchronized (this) { // Upon exit from this method, the state is always >= cs_CLOSED connectionState = (connectionState == cs_APP_CLOSED) ? cs_APP_CLOSED : cs_CLOSED; // notify any threads waiting for the closing to finish this.notifyAll(); } if (closeSocketCalled) { // Dispose of ciphers since we've closed socket disposeCiphers(); } if (cachedThrowable != null) { /* * Rethrow the error to the calling method * The Throwable caught can only be an Error or RuntimeException */ if (cachedThrowable instanceof Error) throw (Error) cachedThrowable; if (cachedThrowable instanceof RuntimeException) throw (RuntimeException) cachedThrowable; } } } /** * Reads a close_notify or a fatal alert from the input stream. * Keep reading records until we get a close_notify or until * the connection is otherwise closed. The close_notify or alert * might be read by another reader, * which will then process the close and set the connection state. */ void waitForClose(boolean rethrow) throws IOException { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", waiting for close_notify or alert: state " + getConnectionState()); } try { int state; while (((state = getConnectionState()) != cs_CLOSED) && (state != cs_ERROR) && (state != cs_APP_CLOSED)) { // create the InputRecord if it isn't intialized. if (inrec == null) { inrec = new InputRecord(); } // Ask for app data and then throw it away try { readRecord(inrec, true); } catch (SocketTimeoutException e) { // if time out, ignore the exception and continue } } inrec = null; } catch (IOException e) { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", Exception while waiting for close " +e); } if (rethrow) { throw e; // pass exception up } } } /** * Called by closeInternal() only. Be sure to consider the * synchronization locks carefully before calling it elsewhere. */ private void disposeCiphers() { // See comment in changeReadCiphers() synchronized (readLock) { readCipher.dispose(); } // See comment in changeReadCiphers() writeLock.lock(); try { writeCipher.dispose(); } finally { writeLock.unlock(); } } // // EXCEPTION AND ALERT HANDLING // /** * Handle an exception. This method is called by top level exception * handlers (in read(), write()) to make sure we always shutdown the * connection correctly and do not pass runtime exception to the * application. */ void handleException(Exception e) throws IOException { handleException(e, true); } /** * Handle an exception. This method is called by top level exception * handlers (in read(), write(), startHandshake()) to make sure we * always shutdown the connection correctly and do not pass runtime * exception to the application. * * This method never returns normally, it always throws an IOException. * * We first check if the socket has already been shutdown because of an * error. If so, we just rethrow the exception. If the socket has not * been shutdown, we sent a fatal alert and remember the exception. * * @param e the Exception * @param resumable indicates the caller process is resumable from the * exception. If <code>resumable</code>, the socket will be * reserved for exceptions like timeout; otherwise, the socket * will be closed, no further communications could be done. */ synchronized private void handleException(Exception e, boolean resumable) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", handling exception: " + e.toString()); } // don't close the Socket in case of timeouts or interrupts if // the process is resumable. if (e instanceof InterruptedIOException && resumable) { throw (IOException)e; } // if we've already shutdown because of an error, // there is nothing to do except rethrow the exception if (closeReason != null) { if (e instanceof IOException) { // includes SSLException throw (IOException)e; } else { // this is odd, not an IOException. // normally, this should not happen // if closeReason has been already been set throw Alerts.getSSLException(Alerts.alert_internal_error, e, "Unexpected exception"); } } // need to perform error shutdown boolean isSSLException = (e instanceof SSLException); if ((isSSLException == false) && (e instanceof IOException)) { // IOException from the socket // this means the TCP connection is already dead // we call fatal just to set the error status try { fatal(Alerts.alert_unexpected_message, e); } catch (IOException ee) { // ignore (IOException wrapped in SSLException) } // rethrow original IOException throw (IOException)e; } // must be SSLException or RuntimeException byte alertType; if (isSSLException) { if (e instanceof SSLHandshakeException) { alertType = Alerts.alert_handshake_failure; } else { alertType = Alerts.alert_unexpected_message; } } else { alertType = Alerts.alert_internal_error; } fatal(alertType, e); } /* * Send a warning alert. */ void warning(byte description) { sendAlert(Alerts.alert_warning, description); } synchronized void fatal(byte description, String diagnostic) throws IOException { fatal(description, diagnostic, null); } synchronized void fatal(byte description, Throwable cause) throws IOException { fatal(description, null, cause); } /* * Send a fatal alert, and throw an exception so that callers will * need to stand on their heads to accidentally continue processing. */ synchronized void fatal(byte description, String diagnostic, Throwable cause) throws IOException { if ((input != null) && (input.r != null)) { input.r.close(); } sess.invalidate(); if (handshakeSession != null) { handshakeSession.invalidate(); } int oldState = connectionState; if (connectionState < cs_ERROR) { connectionState = cs_ERROR; } /* * Has there been an error received yet? If not, remember it. * By RFC 2246, we don't bother waiting for a response. * Fatal errors require immediate shutdown. */ if (closeReason == null) { /* * Try to clear the kernel buffer to avoid TCP connection resets. */ if (oldState == cs_HANDSHAKE) { sockInput.skip(sockInput.available()); } // If the description equals -1, the alert won't be sent to peer. if (description != -1) { sendAlert(Alerts.alert_fatal, description); } if (cause instanceof SSLException) { // only true if != null closeReason = (SSLException)cause; } else { closeReason = Alerts.getSSLException(description, cause, diagnostic); } } /* * Clean up our side. */ closeSocket(); // Another thread may have disposed the ciphers during closing if (connectionState < cs_CLOSED) { connectionState = (oldState == cs_APP_CLOSED) ? cs_APP_CLOSED : cs_CLOSED; // We should lock readLock and writeLock if no deadlock risks. // See comment in changeReadCiphers() readCipher.dispose(); writeCipher.dispose(); } throw closeReason; } /* * Process an incoming alert ... caller must already have synchronized * access to "this". */ private void recvAlert(InputRecord r) throws IOException { byte level = (byte)r.read(); byte description = (byte)r.read(); if (description == -1) { // check for short message fatal(Alerts.alert_illegal_parameter, "Short alert message"); } if (debug != null && (Debug.isOn("record") || Debug.isOn("handshake"))) { synchronized (System.out) { System.out.print(threadName()); System.out.print(", RECV " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); } else if (level == Alerts.alert_warning) { System.out.print("warning, "); } else { System.out.print("<level " + (0x0ff & level) + ">, "); } System.out.println(Alerts.alertDescription(description)); } } if (level == Alerts.alert_warning) { if (description == Alerts.alert_close_notify) { if (connectionState == cs_HANDSHAKE) { fatal(Alerts.alert_unexpected_message, "Received close_notify during handshake"); } else { closeInternal(false); // reply to close } } else { // // The other legal warnings relate to certificates, // e.g. no_certificate, bad_certificate, etc; these // are important to the handshaking code, which can // also handle illegal protocol alerts if needed. // if (handshaker != null) { handshaker.handshakeAlert(description); } } } else { // fatal or unknown level String reason = "Received fatal alert: " + Alerts.alertDescription(description); if (closeReason == null) { closeReason = Alerts.getSSLException(description, reason); } fatal(Alerts.alert_unexpected_message, reason); } } /* * Emit alerts. Caller must have synchronized with "this". */ private void sendAlert(byte level, byte description) { // the connectionState cannot be cs_START if (connectionState >= cs_SENT_CLOSE) { return; } // For initial handshaking, don't send alert message to peer if // handshaker has not started. if (connectionState == cs_HANDSHAKE && (handshaker == null || !handshaker.started())) { return; } OutputRecord r = new OutputRecord(Record.ct_alert); r.setVersion(protocolVersion); boolean useDebug = debug != null && Debug.isOn("ssl"); if (useDebug) { synchronized (System.out) { System.out.print(threadName()); System.out.print(", SEND " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); } else if (level == Alerts.alert_warning) { System.out.print("warning, "); } else { System.out.print("<level = " + (0x0ff & level) + ">, "); } System.out.println("description = " + Alerts.alertDescription(description)); } } r.write(level); r.write(description); try { writeRecord(r); } catch (IOException e) { if (useDebug) { System.out.println(threadName() + ", Exception sending alert: " + e); } } } // // VARIOUS OTHER METHODS // /* * When a connection finishes handshaking by enabling use of a newly * negotiated session, each end learns about it in two halves (read, * and write). When both read and write ciphers have changed, and the * last handshake message has been read, the connection has joined * (rejoined) the new session. * * NOTE: The SSLv3 spec is rather unclear on the concepts here. * Sessions don't change once they're established (including cipher * suite and master secret) but connections can join them (and leave * them). They're created by handshaking, though sometime handshaking * causes connections to join up with pre-established sessions. */ private void changeReadCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create decompressor CipherBox oldCipher = readCipher; try { readCipher = handshaker.newReadCipher(); readMAC = handshaker.newReadMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e);Lines 1501–2000 of 2493. Previous lines Load more
*/ private void closeInternal(boolean selfInitiated) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", called closeInternal(" + selfInitiated + ")"); } int state = getConnectionState(); boolean closeSocketCalled = false; Throwable cachedThrowable = null; try { switch (state) { /* * java.net code sometimes closes sockets "early", when * we can't actually do I/O on them. */ case cs_START: break; /* * If we're closing down due to error, we already sent (or else * received) the fatal alert ... no niceties, blow the connection * away as quickly as possible (even if we didn't allocate the * socket ourselves; it's unusable, regardless). */ case cs_ERROR: closeSocket(); break; /* * Sometimes close() gets called more than once. */ case cs_CLOSED: case cs_APP_CLOSED: break; /* * Otherwise we indicate clean termination. */ // case cs_HANDSHAKE: // case cs_DATA: // case cs_RENEGOTIATE: // case cs_SENT_CLOSE: default: synchronized (this) { if (((state = getConnectionState()) == cs_CLOSED) || (state == cs_ERROR) || (state == cs_APP_CLOSED)) { return; // connection was closed while we waited } if (state != cs_SENT_CLOSE) { try { warning(Alerts.alert_close_notify); connectionState = cs_SENT_CLOSE; } catch (Throwable th) { // we need to ensure socket is closed out // if we encounter any errors. connectionState = cs_ERROR; // cache this for later use cachedThrowable = th; closeSocketCalled = true; closeSocket(selfInitiated); } } } // If state was cs_SENT_CLOSE before, we don't do the actual // closing since it is already in progress. if (state == cs_SENT_CLOSE) { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", close invoked again; state = " + getConnectionState()); } if (selfInitiated == false) { // We were called because a close_notify message was // received. This may be due to another thread calling // read() or due to our call to waitForClose() below. // In either case, just return. return; } // Another thread explicitly called close(). We need to // wait for the closing to complete before returning. synchronized (this) { while (connectionState < cs_CLOSED) { try { this.wait(); } catch (InterruptedException e) { // ignore } } } if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", after primary close; state = " + getConnectionState()); } return; } if (!closeSocketCalled) { closeSocketCalled = true; closeSocket(selfInitiated); } break; } } finally { synchronized (this) { // Upon exit from this method, the state is always >= cs_CLOSED connectionState = (connectionState == cs_APP_CLOSED) ? cs_APP_CLOSED : cs_CLOSED; // notify any threads waiting for the closing to finish this.notifyAll(); } if (closeSocketCalled) { // Dispose of ciphers since we've closed socket disposeCiphers(); } if (cachedThrowable != null) { /* * Rethrow the error to the calling method * The Throwable caught can only be an Error or RuntimeException */ if (cachedThrowable instanceof Error) throw (Error) cachedThrowable; if (cachedThrowable instanceof RuntimeException) throw (RuntimeException) cachedThrowable; } } } /** * Reads a close_notify or a fatal alert from the input stream. * Keep reading records until we get a close_notify or until * the connection is otherwise closed. The close_notify or alert * might be read by another reader, * which will then process the close and set the connection state. */ void waitForClose(boolean rethrow) throws IOException { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", waiting for close_notify or alert: state " + getConnectionState()); } try { int state; while (((state = getConnectionState()) != cs_CLOSED) && (state != cs_ERROR) && (state != cs_APP_CLOSED)) { // create the InputRecord if it isn't intialized. if (inrec == null) { inrec = new InputRecord(); } // Ask for app data and then throw it away try { readRecord(inrec, true); } catch (SocketTimeoutException e) { // if time out, ignore the exception and continue } } inrec = null; } catch (IOException e) { if (debug != null && Debug.isOn("ssl")) { System.out.println(threadName() + ", Exception while waiting for close " +e); } if (rethrow) { throw e; // pass exception up } } } /** * Called by closeInternal() only. Be sure to consider the * synchronization locks carefully before calling it elsewhere. */ private void disposeCiphers() { // See comment in changeReadCiphers() synchronized (readLock) { readCipher.dispose(); } // See comment in changeReadCiphers() writeLock.lock(); try { writeCipher.dispose(); } finally { writeLock.unlock(); } } // // EXCEPTION AND ALERT HANDLING // /** * Handle an exception. This method is called by top level exception * handlers (in read(), write()) to make sure we always shutdown the * connection correctly and do not pass runtime exception to the * application. */ void handleException(Exception e) throws IOException { handleException(e, true); } /** * Handle an exception. This method is called by top level exception * handlers (in read(), write(), startHandshake()) to make sure we * always shutdown the connection correctly and do not pass runtime * exception to the application. * * This method never returns normally, it always throws an IOException. * * We first check if the socket has already been shutdown because of an * error. If so, we just rethrow the exception. If the socket has not * been shutdown, we sent a fatal alert and remember the exception. * * @param e the Exception * @param resumable indicates the caller process is resumable from the * exception. If <code>resumable</code>, the socket will be * reserved for exceptions like timeout; otherwise, the socket * will be closed, no further communications could be done. */ synchronized private void handleException(Exception e, boolean resumable) throws IOException { if ((debug != null) && Debug.isOn("ssl")) { System.out.println(threadName() + ", handling exception: " + e.toString()); } // don't close the Socket in case of timeouts or interrupts if // the process is resumable. if (e instanceof InterruptedIOException && resumable) { throw (IOException)e; } // if we've already shutdown because of an error, // there is nothing to do except rethrow the exception if (closeReason != null) { if (e instanceof IOException) { // includes SSLException throw (IOException)e; } else { // this is odd, not an IOException. // normally, this should not happen // if closeReason has been already been set throw Alerts.getSSLException(Alerts.alert_internal_error, e, "Unexpected exception"); } } // need to perform error shutdown boolean isSSLException = (e instanceof SSLException); if ((isSSLException == false) && (e instanceof IOException)) { // IOException from the socket // this means the TCP connection is already dead // we call fatal just to set the error status try { fatal(Alerts.alert_unexpected_message, e); } catch (IOException ee) { // ignore (IOException wrapped in SSLException) } // rethrow original IOException throw (IOException)e; } // must be SSLException or RuntimeException byte alertType; if (isSSLException) { if (e instanceof SSLHandshakeException) { alertType = Alerts.alert_handshake_failure; } else { alertType = Alerts.alert_unexpected_message; } } else { alertType = Alerts.alert_internal_error; } fatal(alertType, e); } /* * Send a warning alert. */ void warning(byte description) { sendAlert(Alerts.alert_warning, description); } synchronized void fatal(byte description, String diagnostic) throws IOException { fatal(description, diagnostic, null); } synchronized void fatal(byte description, Throwable cause) throws IOException { fatal(description, null, cause); } /* * Send a fatal alert, and throw an exception so that callers will * need to stand on their heads to accidentally continue processing. */ synchronized void fatal(byte description, String diagnostic, Throwable cause) throws IOException { if ((input != null) && (input.r != null)) { input.r.close(); } sess.invalidate(); if (handshakeSession != null) { handshakeSession.invalidate(); } int oldState = connectionState; if (connectionState < cs_ERROR) { connectionState = cs_ERROR; } /* * Has there been an error received yet? If not, remember it. * By RFC 2246, we don't bother waiting for a response. * Fatal errors require immediate shutdown. */ if (closeReason == null) { /* * Try to clear the kernel buffer to avoid TCP connection resets. */ if (oldState == cs_HANDSHAKE) { sockInput.skip(sockInput.available()); } // If the description equals -1, the alert won't be sent to peer. if (description != -1) { sendAlert(Alerts.alert_fatal, description); } if (cause instanceof SSLException) { // only true if != null closeReason = (SSLException)cause; } else { closeReason = Alerts.getSSLException(description, cause, diagnostic); } } /* * Clean up our side. */ closeSocket(); // Another thread may have disposed the ciphers during closing if (connectionState < cs_CLOSED) { connectionState = (oldState == cs_APP_CLOSED) ? cs_APP_CLOSED : cs_CLOSED; // We should lock readLock and writeLock if no deadlock risks. // See comment in changeReadCiphers() readCipher.dispose(); writeCipher.dispose(); } throw closeReason; } /* * Process an incoming alert ... caller must already have synchronized * access to "this". */ private void recvAlert(InputRecord r) throws IOException { byte level = (byte)r.read(); byte description = (byte)r.read(); if (description == -1) { // check for short message fatal(Alerts.alert_illegal_parameter, "Short alert message"); } if (debug != null && (Debug.isOn("record") || Debug.isOn("handshake"))) { synchronized (System.out) { System.out.print(threadName()); System.out.print(", RECV " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); } else if (level == Alerts.alert_warning) { System.out.print("warning, "); } else { System.out.print("<level " + (0x0ff & level) + ">, "); } System.out.println(Alerts.alertDescription(description)); } } if (level == Alerts.alert_warning) { if (description == Alerts.alert_close_notify) { if (connectionState == cs_HANDSHAKE) { fatal(Alerts.alert_unexpected_message, "Received close_notify during handshake"); } else { closeInternal(false); // reply to close } } else { // // The other legal warnings relate to certificates, // e.g. no_certificate, bad_certificate, etc; these // are important to the handshaking code, which can // also handle illegal protocol alerts if needed. // if (handshaker != null) { handshaker.handshakeAlert(description); } } } else { // fatal or unknown level String reason = "Received fatal alert: " + Alerts.alertDescription(description); if (closeReason == null) { closeReason = Alerts.getSSLException(description, reason); } fatal(Alerts.alert_unexpected_message, reason); } } /* * Emit alerts. Caller must have synchronized with "this". */ private void sendAlert(byte level, byte description) { // the connectionState cannot be cs_START if (connectionState >= cs_SENT_CLOSE) { return; } // For initial handshaking, don't send alert message to peer if // handshaker has not started. if (connectionState == cs_HANDSHAKE && (handshaker == null || !handshaker.started())) { return; } OutputRecord r = new OutputRecord(Record.ct_alert); r.setVersion(protocolVersion); boolean useDebug = debug != null && Debug.isOn("ssl"); if (useDebug) { synchronized (System.out) { System.out.print(threadName()); System.out.print(", SEND " + protocolVersion + " ALERT: "); if (level == Alerts.alert_fatal) { System.out.print("fatal, "); } else if (level == Alerts.alert_warning) { System.out.print("warning, "); } else { System.out.print("<level = " + (0x0ff & level) + ">, "); } System.out.println("description = " + Alerts.alertDescription(description)); } } r.write(level); r.write(description); try { writeRecord(r); } catch (IOException e) { if (useDebug) { System.out.println(threadName() + ", Exception sending alert: " + e); } } } // // VARIOUS OTHER METHODS // /* * When a connection finishes handshaking by enabling use of a newly * negotiated session, each end learns about it in two halves (read, * and write). When both read and write ciphers have changed, and the * last handshake message has been read, the connection has joined * (rejoined) the new session. * * NOTE: The SSLv3 spec is rather unclear on the concepts here. * Sessions don't change once they're established (including cipher * suite and master secret) but connections can join them (and leave * them). They're created by handshaking, though sometime handshaking * causes connections to join up with pre-established sessions. */ private void changeReadCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create decompressor CipherBox oldCipher = readCipher; try { readCipher = handshaker.newReadCipher(); readMAC = handshaker.newReadMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e);