You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2015/06/18 19:10:09 UTC

svn commit: r1686276 [1/2] - in /tomcat/trunk: ./ java/org/apache/catalina/core/ java/org/apache/tomcat/jni/ java/org/apache/tomcat/jni/socket/

Author: remm
Date: Thu Jun 18 17:10:08 2015
New Revision: 1686276

URL: http://svn.apache.org/r1686276
Log:
- Add JNI API updates from Netty and Twitter. jni.socket.* is not useful to Tomcat at the moment, but is a NIO2 style API on top of APR.
- Update recommended native library version to 1.2. If not using the new OpenSSL features, this shouldn't break and I prefer not requiring a trunk build.

Added:
    tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java
    tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java
    tomcat/trunk/java/org/apache/tomcat/jni/socket/
    tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java
    tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java
    tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java
Modified:
    tomcat/trunk/NOTICE
    tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java
    tomcat/trunk/java/org/apache/tomcat/jni/SSL.java
    tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java

Modified: tomcat/trunk/NOTICE
URL: http://svn.apache.org/viewvc/tomcat/trunk/NOTICE?rev=1686276&r1=1686275&r2=1686276&view=diff
==============================================================================
--- tomcat/trunk/NOTICE (original)
+++ tomcat/trunk/NOTICE Thu Jun 18 17:10:08 2015
@@ -4,6 +4,12 @@ Copyright 1999-2015 The Apache Software
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 
+This software contains code derived from netty-native
+developed by the Netty project
+(http://netty.io, https://github.com/netty/netty-tcnative/)
+and from finagle-native developed at Twitter
+(https://github.com/twitter/finagle).
+
 The Windows Installer is built with the Nullsoft
 Scriptable Install System (NSIS), which is
 open source software.  The original software and
@@ -15,6 +21,13 @@ JDT Core Batch Compiler component, which
 The original software and related information is available at
 http://www.eclipse.org/jdt/core/.
 
+For portions of the Tomcat JNI OpenSSL API and the OpenSSL JSSE integration
+The org.apache.tomcat.jni and the org.apache.tomcat.net.openssl packages
+are derivative work originating from the Netty project and the finagle-native
+project developed at Twitter
+* Copyright 2014 The Netty Project
+* Copyright 2014 Twitter
+
 The original XML Schemas for Java EE Deployment Descriptors:
  - javaee_5.xsd
  - javaee_web_services_1_2.xsd

Modified: tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java?rev=1686276&r1=1686275&r2=1686276&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java (original)
+++ tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java Thu Jun 18 17:10:08 2015
@@ -68,8 +68,8 @@ public class AprLifecycleListener
     protected static final int TCN_REQUIRED_MAJOR = 1;
     protected static final int TCN_REQUIRED_MINOR = 1;
     protected static final int TCN_REQUIRED_PATCH = 32;
-    protected static final int TCN_RECOMMENDED_MINOR = 1;
-    protected static final int TCN_RECOMMENDED_PV = 33;
+    protected static final int TCN_RECOMMENDED_MINOR = 2;
+    protected static final int TCN_RECOMMENDED_PV = 0;
 
 
     // ---------------------------------------------- Properties

Added: tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java?rev=1686276&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java Thu Jun 18 17:10:08 2015
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.jni;
+
+/**
+ * Is called during handshake and hooked into openssl via {@code SSL_CTX_set_cert_verify_callback}.
+ */
+public interface CertificateVerifier {
+
+    /**
+     * Returns {@code true} if the passed in certificate chain could be verified and so the handshake
+     * should be successful, {@code false} otherwise.
+     *
+     * @param ssl               the SSL instance
+     * @param x509              the {@code X509} certificate chain
+     * @param authAlgorithm     the auth algorithm
+     * @return verified         {@code true} if verified successful, {@code false} otherwise
+     */
+    boolean verify(long ssl, byte[][] x509, String authAlgorithm);
+}

Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSL.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSL.java?rev=1686276&r1=1686275&r2=1686276&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/SSL.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/jni/SSL.java Thu Jun 18 17:10:08 2015
@@ -226,6 +226,14 @@ public final class SSL {
      * Add certificate chain number to that flag (0 ... verify depth)
      */
     public static final int SSL_INFO_CLIENT_CERT_CHAIN         = 0x0400;
+
+    /* Only support OFF and SERVER for now */
+    public static final long SSL_SESS_CACHE_OFF = 0x0000;
+    public static final long SSL_SESS_CACHE_SERVER = 0x0002;
+
+    public static final int SSL_SELECTOR_FAILURE_NO_ADVERTISE = 0;
+    public static final int SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL = 1;
+
     /* Return OpenSSL version number */
     public static native int version();
 
@@ -347,4 +355,301 @@ public final class SSL {
      * @return true if all SSL_OP_* are supported by OpenSSL library.
      */
     public static native boolean hasOp(int op);
+
+    /*
+     * Begin Twitter API additions
+     */
+
+    public static final int SSL_SENT_SHUTDOWN = 1;
+    public static final int SSL_RECEIVED_SHUTDOWN = 2;
+
+    public static final int SSL_ERROR_NONE             = 0;
+    public static final int SSL_ERROR_SSL              = 1;
+    public static final int SSL_ERROR_WANT_READ        = 2;
+    public static final int SSL_ERROR_WANT_WRITE       = 3;
+    public static final int SSL_ERROR_WANT_X509_LOOKUP = 4;
+    public static final int SSL_ERROR_SYSCALL          = 5; /* look at error stack/return value/errno */
+    public static final int SSL_ERROR_ZERO_RETURN      = 6;
+    public static final int SSL_ERROR_WANT_CONNECT     = 7;
+    public static final int SSL_ERROR_WANT_ACCEPT      = 8;
+
+    /**
+     * SSL_new
+     * @param ctx Server or Client context to use.
+     * @param server if true configure SSL instance to use accept handshake routines
+     *               if false configure SSL instance to use connect handshake routines
+     * @return pointer to SSL instance (SSL *)
+     */
+    public static native long newSSL(long ctx, boolean server);
+
+    /**
+     * SSL_set_bio
+     * @param ssl SSL pointer (SSL *)
+     * @param rbio read BIO pointer (BIO *)
+     * @param wbio write BIO pointer (BIO *)
+     */
+    public static native void setBIO(long ssl, long rbio, long wbio);
+
+    /**
+     * SSL_get_error
+     * @param ssl SSL pointer (SSL *)
+     * @param ret TLS/SSL I/O return value
+     */
+    public static native int getError(long ssl, int ret);
+
+    /**
+     * BIO_ctrl_pending
+     * @param bio BIO pointer (BIO *)
+     * @return
+     */
+    public static native int pendingWrittenBytesInBIO(long bio);
+
+    /**
+     * SSL_pending
+     * @param ssl SSL pointer (SSL *)
+     * @return
+     */
+    public static native int pendingReadableBytesInSSL(long ssl);
+
+    /**
+     * BIO_write
+     * @param bio
+     * @param wbuf
+     * @param wlen
+     * @return
+     */
+    public static native int writeToBIO(long bio, long wbuf, int wlen);
+
+    /**
+     * BIO_read
+     * @param bio
+     * @param rbuf
+     * @param rlen
+     * @return
+     */
+    public static native int readFromBIO(long bio, long rbuf, int rlen);
+
+    /**
+     * SSL_write
+     * @param ssl the SSL instance (SSL *)
+     * @param wbuf
+     * @param wlen
+     * @return
+     */
+    public static native int writeToSSL(long ssl, long wbuf, int wlen);
+
+    /**
+     * SSL_read
+     * @param ssl the SSL instance (SSL *)
+     * @param rbuf
+     * @param rlen
+     * @return
+     */
+    public static native int readFromSSL(long ssl, long rbuf, int rlen);
+
+    /**
+     * SSL_get_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native int getShutdown(long ssl);
+
+    /**
+     * SSL_set_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @param mode
+     */
+    public static native void setShutdown(long ssl, int mode);
+
+    /**
+     * SSL_free
+     * @param ssl the SSL instance (SSL *)
+     */
+    public static native void freeSSL(long ssl);
+
+    /**
+     * Wire up internal and network BIOs for the given SSL instance.
+     *
+     * <b>Warning: you must explicitly free this resource by calling freeBIO</b>
+     *
+     * While the SSL's internal/application data BIO will be freed when freeSSL is called on
+     * the provided SSL instance, you must call freeBIO on the returned network BIO.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return pointer to the Network BIO (BIO *)
+     */
+    public static native long makeNetworkBIO(long ssl);
+
+    /**
+     * BIO_free
+     * @param bio
+     */
+    public static native void freeBIO(long bio);
+
+    /**
+     * BIO_flush
+     * @param bio
+     */
+    public static native void flushBIO(long bio);
+
+    /**
+     * SSL_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native int shutdownSSL(long ssl);
+
+    /**
+     * Get the error number representing the last error OpenSSL encountered on this thread.
+     * @return
+     */
+    public static native int getLastErrorNumber();
+
+    /**
+     * SSL_get_cipher
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getCipherForSSL(long ssl);
+
+    /**
+     * SSL_get_version
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getVersion(long ssl);
+
+    /**
+     * SSL_do_handshake
+     * @param ssl the SSL instance (SSL *)
+     */
+    public static native int doHandshake(long ssl);
+
+    /**
+     * SSL_in_init
+     * @param SSL
+     * @return
+     */
+    public static native int isInInit(long SSL);
+
+    /**
+     * SSL_get0_next_proto_negotiated
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getNextProtoNegotiated(long ssl);
+
+    /*
+     * End Twitter API Additions
+     */
+
+    /**
+     * SSL_get0_alpn_selected
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getAlpnSelected(long ssl);
+
+    /**
+     * Get the peer certificate chain or {@code null} if non was send.
+     */
+    public static native byte[][] getPeerCertChain(long ssl);
+
+    /**
+     * Get the peer certificate or {@code null} if non was send.
+     */
+    public static native byte[] getPeerCertificate(long ssl);
+    /*
+     * Get the error number representing for the given {@code errorNumber}.
+     */
+    public static native String getErrorString(long errorNumber);
+
+    /**
+     * SSL_get_time
+     * @param ssl the SSL instance (SSL *)
+     * @return returns the time at which the session ssl was established. The time is given in seconds since the Epoch
+     */
+    public static native long getTime(long ssl);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA Certificates
+     * in Client Certificate verification.
+     * <br />
+     * This directive sets the Certificate verification level for the Client
+     * Authentication. Notice that this directive can be used both in per-server
+     * and per-directory context. In per-server context it applies to the client
+     * authentication process used in the standard SSL handshake when a connection
+     * is established. In per-directory context it forces a SSL renegotiation with
+     * the reconfigured client verification level after the HTTP request was read
+     * but before the HTTP response is sent.
+     * <br />
+     * The following levels are available for level:
+     * <pre>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully) verifiable
+     * </pre>
+     * <br />
+     * The depth actually is the maximum number of intermediate certificate issuers,
+     * i.e. the number of CA certificates which are max allowed to be followed while
+     * verifying the client certificate. A depth of 0 means that self-signed client
+     * certificates are accepted only, the default depth of 1 means the client
+     * certificate can be self-signed or has to be signed by a CA which is directly
+     * known to the server (i.e. the CA's certificate is under
+     * {@code setCACertificatePath}, etc.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @param level Type of Client Certificate verification.
+     * @param depth Maximum depth of CA Certificates in Client Certificate
+     *              verification.
+     */
+    public static native void setVerify(long ssl, int level, int depth);
+
+    /**
+     * Set OpenSSL Option.
+     * @param ssl the SSL instance (SSL *)
+     * @param options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native void setOptions(long ssl, int options);
+
+    /**
+     * Get OpenSSL Option.
+     * @param ssl the SSL instance (SSL *)
+     * @return options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native int getOptions(long ssl);
+
+    /**
+     * Returns all Returns the cipher suites that are available for negotiation in an SSL handshake.
+     * @param ssl the SSL instance (SSL *)
+     * @return ciphers
+     */
+    public static native String[] getCiphers(long ssl);
+
+    /**
+     * Returns the cipher suites available for negotiation in SSL handshake.
+     * <br />
+     * This complex directive uses a colon-separated cipher-spec string consisting
+     * of OpenSSL cipher specifications to configure the Cipher Suite the client
+     * is permitted to negotiate in the SSL handshake phase. Notice that this
+     * directive can be used both in per-server and per-directory context.
+     * In per-server context it applies to the standard SSL handshake when a
+     * connection is established. In per-directory context it forces a SSL
+     * renegotiation with the reconfigured Cipher Suite after the HTTP request
+     * was read but before the HTTP response is sent.
+     * @param ssl the SSL instance (SSL *)
+     * @param ciphers an SSL cipher specification
+     */
+    public static native boolean setCipherSuites(long ssl, String ciphers)
+            throws Exception;
+
+    /**
+     * Returns the ID of the session as byte array representation.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return the session as byte array representation obtained via SSL_SESSION_get_id.
+     */
+    public static native byte[] getSessionId(long ssl);
 }

Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java?rev=1686276&r1=1686275&r2=1686276&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java Thu Jun 18 17:10:08 2015
@@ -14,6 +14,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+
 package org.apache.tomcat.jni;
 
 import java.util.Map;
@@ -96,6 +97,13 @@ public final class SSLContext {
     public static native void setOptions(long ctx, int options);
 
     /**
+     * Get OpenSSL Option.
+     * @param ctx Server or Client context to use.
+     * @return options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native int getOptions(long ctx);
+
+    /**
      * Clears OpenSSL Options.
      * @param ctx Server or Client context to use.
      * @param options  See SSL.SSL_OP_* for option flags.
@@ -214,6 +222,62 @@ public final class SSLContext {
         throws Exception;
 
     /**
+     * Set the size of the internal session cache.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_set_cache_size.html
+     */
+    public static native long setSessionCacheSize(long ctx, long size);
+
+    /**
+     * Get the size of the internal session cache.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_get_cache_size.html
+     */
+    public static native long getSessionCacheSize(long ctx);
+
+    /**
+     * Set the timeout for the internal session cache in seconds.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html
+     */
+    public static native long setSessionCacheTimeout(long ctx, long timeoutSeconds);
+
+    /**
+     * Get the timeout for the internal session cache in seconds.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html
+     */
+    public static native long getSessionCacheTimeout(long ctx);
+
+    /**
+     * Set the mode of the internal session cache and return the previous used mode.
+     */
+    public static native long setSessionCacheMode(long ctx, long mode);
+
+    /**
+     * Get the mode of the current used internal session cache.
+     */
+    public static native long getSessionCacheMode(long ctx);
+
+    /**
+     * Session resumption statistics methods.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html
+     */
+    public static native long sessionAccept(long ctx);
+    public static native long sessionAcceptGood(long ctx);
+    public static native long sessionAcceptRenegotiate(long ctx);
+    public static native long sessionCacheFull(long ctx);
+    public static native long sessionCbHits(long ctx);
+    public static native long sessionConnect(long ctx);
+    public static native long sessionConnectGood(long ctx);
+    public static native long sessionConnectRenegotiate(long ctx);
+    public static native long sessionHits(long ctx);
+    public static native long sessionMisses(long ctx);
+    public static native long sessionNumber(long ctx);
+    public static native long sessionTimeouts(long ctx);
+
+    /**
+     * Set TLS session keys. This allows us to share keys across TFEs.
+     */
+    public static native void setSessionTicketKeys(long ctx, byte[] keys);
+
+    /**
      * Set File and Directory of concatenated PEM-encoded CA Certificates
      * for Client Auth
      * <br>
@@ -377,4 +441,72 @@ public final class SSLContext {
          */
         public long getSslContext(String sniHostName);
     }
+
+    /**
+     * Allow to hook {@link CertificateVerifier} into the handshake processing.
+     * This will call {@code SSL_CTX_set_cert_verify_callback} and so replace the default verification
+     * callback used by openssl
+     * @param ctx Server or Client context to use.
+     * @param verifier the verifier to call during handshake.
+     */
+    public static native void setCertVerifyCallback(long ctx, CertificateVerifier verifier);
+
+    /**
+     * Set next protocol for next protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param nextProtos comma delimited list of protocols in priority order
+     *
+     * @deprecated use {@link #setNpnProtos(long, String[], int)}
+     */
+    @Deprecated
+    public static void setNextProtos(long ctx, String nextProtos) {
+        setNpnProtos(ctx, nextProtos.split(","), SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL);
+    }
+
+    /**
+     * Set next protocol for next protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param nextProtos protocols in priority order
+     * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE}
+     *                                and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL}
+     */
+    public static native void setNpnProtos(long ctx, String[] nextProtos, int selectorFailureBehavior);
+
+    /**
+     * Set application layer protocol for application layer protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param alpnProtos protocols in priority order
+     * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE}
+     *                                and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL}
+     */
+    public static native void setAlpnProtos(long ctx, String[] alpnProtos, int selectorFailureBehavior);
+
+    /**
+     * Set DH parameters
+     * @param ctx Server context to use.
+     * @param cert DH param file (can be generated from e.g. {@code openssl dhparam -rand - 2048 > dhparam.pem} -
+     *             see the <a href="https://www.openssl.org/docs/apps/dhparam.html">OpenSSL documentation</a>).
+     */
+    public static native void setTmpDH(long ctx, String cert)
+            throws Exception;
+
+    /**
+     * Set ECDH elliptic curve by name
+     * @param ctx Server context to use.
+     * @param curveName the name of the elliptic curve to use
+     *             (available names can be obtained from {@code openssl ecparam -list_curves}).
+     */
+    public static native void setTmpECDHByCurveName(long ctx, String curveName)
+            throws Exception;
+
+    /**
+     * Set the context within which session be reused (server side only)
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_session_id_context.html
+     *
+     * @param ctx Server context to use.
+     * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name
+     *               of the application and/or the hostname and/or service name
+     * @return {@code true} if success, {@code false} otherwise.
+     */
+    public static native boolean setSessionIdContext(long ctx, byte[] sidCtx);
 }

Added: tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java?rev=1686276&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java Thu Jun 18 17:10:08 2015
@@ -0,0 +1,159 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.jni;
+
+/**
+ * Support TLS extensions and extra methods.
+ *
+ * The methods are separated to make it easier for java code to
+ * support existing native library - it can check if this class can
+ * be loaded in order to use the exensions.
+ *
+ * @author Costin Manolache
+ */
+public final class SSLExt {
+
+
+    /**
+     * Set advertised NPN protocol.
+     * This is only available for recent or patched openssl.
+     *
+     * Example: "\x06spdy/2"
+     *
+     * Works with TLS1, doesn't with SSL2/SSL3
+     *
+     * Servers sends list in ServerHelo, client selects it and
+     * sends it back after ChangeChipher
+     *
+     * Not supported in 1.0.0, seems to be in 1.0.1 and after
+     */
+    public static native int setNPN(long tcctx, byte[] proto, int len);
+
+    /**
+     * Get other side's advertised protocols.
+     * Only works after handshake.
+     */
+    public static native int getNPN(long tcsock, byte[] proto);
+
+    /**
+     * Enabling dump/debugging on the socket. Both raw and decrypted
+     * packets will be logged.
+     */
+    public static native int debug(long tcsock);
+
+    /**
+     * Server: Extract the session data associated with the socket.
+     * Must be saved, keyed by session ID.
+     */
+    public static native byte[] getSessionData(long tcsock);
+
+    /**
+     * Server: Set the session data for a socket.
+     */
+    public static native int setSessionData(long tcsock, byte[] data, int len);
+
+
+    /**
+     * Client: get the ticket received from server, if tickets are supported.
+     */
+    public static native int getTicket(long tcsock, byte[] resBuf);
+
+    /**
+     * Client: set the previously received ticket.
+     */
+    public static native int setTicket(long tcsock, byte[] data, int len);
+
+    /**
+     * Set the key used by server to generate tickets.
+     * Key must be 48 bytes.
+     */
+    public static native int setTicketKeys(long ctx, byte[] data, int len);
+
+    /**
+     * For client side calls. Data should be a \0 terminated string
+     */
+    public static native int setSNI(long tcsock, byte[] data, int len);
+
+    /**
+     * Return the last openssl error
+     */
+    public static native String sslErrReasonErrorString();
+
+    public static native long sslCtxSetMode(long ctx, long mode);
+
+    /* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success
+     * when just a single record has been written): */
+    public static final int SSL_MODE_ENABLE_PARTIAL_WRITE = 0x1;
+
+    /* Make it possible to retry SSL_write() with changed buffer location
+     * (buffer contents must stay the same!); this is not the default to avoid
+     * the misconception that non-blocking SSL_write() behaves like
+     * non-blocking write(): */
+    public static final int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x2;
+
+    /* Don't attempt to automatically build certificate chain */
+    static final int SSL_MODE_NO_AUTO_CHAIN = 0x8;
+
+    /* Save RAM by releasing read and write buffers when they're empty. (SSL3 and
+     * TLS only.)  "Released" buffers are put onto a free-list in the context
+     * or just freed (depending on the context's setting for freelist_max_len). */
+    public static final int SSL_MODE_RELEASE_BUFFERS = 0x10;
+
+    // 1.1
+    //static final int SSL_MODE_HANDSHAKE_CUTTHROUGH = ..;
+
+    /**
+     * SSL_set_mode
+     */
+    public static native long sslSetMode(long tcsock, long mode);
+
+    public static int setNPN(long sslContext, byte[] spdyNPN) {
+        try {
+            return SSLExt.setNPN(sslContext, spdyNPN, spdyNPN.length);
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return -1;
+        }
+    }
+
+    /**
+     * Higher level method, checking if the specified protocol has been
+     * negotiated.
+     */
+    public static boolean checkNPN(long tcsocket, byte[] expected) {
+        byte[] npn = new byte[expected.length + 1];
+        int npnLen = 0;
+        try {
+            npnLen = SSLExt.getNPN(tcsocket, npn);
+            if (npnLen != expected.length) {
+                return false;
+            }
+        } catch (Throwable t) {
+            // ignore
+            return false;
+        }
+        for (int i = 0; i < expected.length; i++) {
+            if (expected[i] != npn[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+
+}

Added: tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java?rev=1686276&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java Thu Jun 18 17:10:08 2015
@@ -0,0 +1,922 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.jni.socket;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.SSLSocket;
+import org.apache.tomcat.jni.Sockaddr;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.jni.socket.AprSocketContext.AprPoller;
+import org.apache.tomcat.jni.socket.AprSocketContext.BlockingPollHandler;
+
+/**
+ * Native socket, using JNI + APR + openssl.
+ *
+ * The socket is non-blocking - you can register either a blocking or non
+ * blocking callback.
+ *
+ * There is no explicit method to register/unregister poll interest -
+ * it is done automatically, when read/write methods return 0.
+ *
+ * To keep the socket polling you must read all the available data, until
+ * read() returns 0. If you want to pause - don't read all input. To resume -
+ * read again until it returns 0.
+ *
+ * Same for write - when write() returns 0 the socket is registered for
+ * write interest.
+ *
+ * You can also use the blocking read/write methods.
+ */
+public class AprSocket implements Runnable {
+
+    private static final Logger log =
+            Logger.getLogger("org.apache.tomcat.jni.socket.AprSocket");
+
+    private static final byte[][] NO_CERTS = new byte[0][];
+
+    static final int CONNECTING = 0x1;
+    static final int CONNECTED = 0x2;
+
+    // Current ( real ) poll status
+    static final int POLLIN_ACTIVE = 0x4;
+    static final int POLLOUT_ACTIVE = 0x8;
+
+    static final int POLL = 0x10;
+
+    static final int SSL_ATTACHED = 0x40;
+
+    // Requested poll status. Set by read/write when needed.
+    // Cleared when polled
+    static final int POLLIN = 0x80;
+    static final int POLLOUT = 0x100;
+
+    static final int ACCEPTED = 0x200;
+    static final int ERROR = 0x400;
+    static final int CLOSED = 0x800;
+
+    static final int READING = 0x1000;
+    static final int WRITING = 0x2000;
+
+    // Not null
+    private final AprSocketContext context;
+
+    // only one - to save per/socket memory - context has similar callbacks.
+    BlockingPollHandler handler;
+
+    // Set while it's associated with a poller - it'll stay associated after
+    // connect until close. Destroy will happen in the poller.
+    // POLL bit indicates if the socket is actually polling.
+    AprPoller poller;
+
+    // Bit field indicating the status and socket should only be accessed with
+    // socketLock protection
+    private int status;
+
+    long socket;
+
+    //long to = 10000;
+
+    // Persistent info about the peer ( SSL, etc )
+    private HostInfo hostInfo;
+
+    AprSocket(AprSocketContext context) {
+        this.context = context;
+    }
+
+    public void recycle() {
+        status = 0;
+        hostInfo = null;
+        handler = null;
+        socket = 0;
+        poller = null;
+    }
+
+    @Override
+    public String toString() {
+        return (context.isServer() ? "AprSrv-" : "AprCli-") +
+                Long.toHexString(socket) + " " + Integer.toHexString(status);
+    }
+
+    public void setHandler(BlockingPollHandler l) {
+        handler = l;
+    }
+
+    private void setNonBlocking() {
+        if (socket != 0 && context.running) {
+            Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 1);
+            Socket.timeoutSet(socket, 0);
+        }
+    }
+
+    /**
+     * Check if the socket is currently registered with a poller.
+     */
+    public boolean isPolling() {
+        synchronized (this) {
+            return (status & POLL) != 0;
+        }
+    }
+
+    public BlockingPollHandler getHandler() {
+        return handler;
+    }
+
+    public AprSocketContext getContext() {
+        return context;
+    }
+
+    AprSocket setHost(HostInfo hi) {
+        hostInfo = hi;
+        return this;
+    }
+
+    /**
+     */
+    public void connect() throws IOException {
+        if (isBlocking()) {
+            // will call handleConnected() at the end.
+            context.connectBlocking(this);
+        } else {
+            synchronized(this) {
+                if ((status & CONNECTING) != 0) {
+                    return;
+                }
+                status |= CONNECTING;
+            }
+            context.connectExecutor.execute(this);
+        }
+    }
+
+
+    // after connection is done, called from a thread pool ( not IO thread )
+    // may block for handshake.
+    void afterConnect() throws IOException {
+        if (hostInfo.secure) {
+            blockingStartTLS();
+        }
+
+        setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+        setStatus(CONNECTED);
+        clearStatus(CONNECTING);
+
+        notifyConnected(false);
+    }
+
+    public HostInfo getHost() {
+        return hostInfo;
+    }
+
+    /**
+     * Write as much data as possible to the socket.
+     *
+     * @param data
+     * @param off
+     * @param len
+     * @return  For both blocking and non-blocking, returns the number of bytes
+     *          written. If no data can be written (e.g. if the buffers are
+     *          full) 0 will be returned.
+     * @throws IOException
+     */
+    public int write(byte[] data, int off, int len, long to) throws IOException {
+        long max = System.currentTimeMillis() + to;
+
+        while (true) {
+            int rc = writeInternal(data, off, len);
+            if (rc < 0) {
+                throw new IOException("Write error " + rc);
+            } else if (rc == 0) {
+                // need poll out - do we need to update polling ?
+                context.findPollerAndAdd(this);
+            } else {
+                return rc;
+            }
+
+            try {
+                long waitTime = max - System.currentTimeMillis();
+                if (waitTime <= 0) {
+                    return 0;
+                }
+                wait(waitTime);
+            } catch (InterruptedException e) {
+                return 0;
+            }
+        }
+    }
+
+    public int write(byte[] data, int off, int len) throws IOException {
+        // In SSL mode, read/write can't be called at the same time.
+        int rc = writeInternal(data, off, len);
+        if (rc < 0) {
+            throw new IOException("Write error " + rc);
+        } else if (rc == 0) {
+            // need poll out - do we need to update polling ?
+            synchronized (this) {
+                context.findPollerAndAdd(this);
+            }
+        }
+        return rc;
+    }
+
+    private int writeInternal(byte[] data, int off, int len) throws IOException {
+        int rt = 0;
+        int sent = 0;
+        synchronized(this) {
+            if ((status & CLOSED) != 0
+                    || socket == 0
+                    || !context.running) {
+                throw new IOException("Closed");
+            }
+            if ((status & WRITING) != 0) {
+                throw new IOException("Write from 2 threads not allowed");
+            }
+            status |= WRITING;
+
+            while (len > 0) {
+                sent = Socket.send(socket, data, off, len);
+                if (sent <= 0) {
+                    break;
+                }
+                off += sent;
+                len -= sent;
+            }
+
+            status &= ~WRITING;
+        }
+
+        if (context.rawDataHandler != null) {
+            context.rawData(this, false, data, off, sent, len, false);
+        }
+
+        if (sent <= 0) {
+            if (sent == -Status.TIMEUP || sent == -Status.EAGAIN || sent == 0) {
+                setStatus(POLLOUT);
+                updatePolling();
+                return rt;
+            }
+            log.warning("apr.send(): Failed to send, closing " + sent);
+            reset();
+            throw new IOException("Error sending " + sent + " " + Error.strerror(-sent));
+        } else {
+            off += sent;
+            len -= sent;
+            rt += sent;
+            return sent;
+        }
+    }
+
+    public int read(byte[] data, int off, int len, long to) throws IOException {
+            int rd = readNB(data, off, len);
+            if (rd == 0) {
+                synchronized(this) {
+                    try {
+                        wait(to);
+                    } catch (InterruptedException e) {
+                        return 0;
+                    }
+                }
+                rd = readNB(data, off, len);
+            }
+            return processReadResult(data, off, len, rd);
+    }
+
+    public int read(byte[] data, int off, int len) throws IOException {
+        return readNB(data, off, len);
+    }
+
+    private int processReadResult(byte[] data, int off, int len, int read)
+            throws IOException {
+        if (context.rawDataHandler != null) {
+            context.rawData(this, true, data, off, read, len, false);
+        }
+
+        if (read > 0) {
+            return read;
+        }
+
+        if (read == 0 || read == -Status.TIMEUP || read == -Status.ETIMEDOUT
+                || read == -Status.EAGAIN) {
+            read = 0;
+            setStatus(POLLIN);
+            updatePolling();
+            return 0;
+        }
+
+        if (read == -Status.APR_EOF || read == -1) {
+            close();
+            return -1;
+        }
+        // abrupt close
+        reset();
+        throw new IOException("apr.read(): " + read + " " + Error.strerror(-read));
+    }
+
+    public int readNB(byte[] data, int off, int len) throws IOException {
+        int read;
+        synchronized(this) {
+            if ((status & CLOSED) != 0
+                    || socket == 0
+                    || !context.running) {
+                return -1;
+            }
+            if ((status & READING) != 0) {
+                throw new IOException("Read from 2 threads not allowed");
+            }
+            status |= READING;
+
+            read = Socket.recv(socket, data, off, len);
+            status &= ~READING;
+        }
+        return processReadResult(data, off, len, read);
+    }
+
+    /*
+      No support for shutdownOutput: SSL is quite tricky.
+      Use close() instead - no read/write will be allowed after.
+
+     */
+
+    public void close() {
+        synchronized (this) {
+            if ((status & CLOSED) != 0 || socket == 0) {
+                return;
+            }
+            status |= CLOSED;
+            status &= ~POLLIN;
+            status &= ~POLLOUT;
+        }
+        if (context.rawDataHandler != null) {
+            context.rawDataHandler.rawData(this, false, null, 0, 0, 0, true);
+        }
+        Socket.close(socket);
+        if (poller == null) {
+            maybeDestroy();
+        } else  {
+            try {
+                poller.requestUpdate(this);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    void maybeDestroy() {
+        synchronized(this) {
+            if (socket == 0 ||
+                    (status & CONNECTING) != 0 || !context.running) {
+                // closed or operation in progress
+                // if context stopped, pool will be destroyed and close
+                // all sockets automatically.
+                return;
+            }
+            if ((status & CLOSED) == 0) {
+                return; // not closed
+            }
+            if ((status & (WRITING | READING)) != 0) {
+                return; // not closed
+            }
+
+            if (context.rawDataHandler != null) {
+                context.rawDataHandler.rawData(this, false, null, -1, -1, -1, true);
+            }
+            if (log.isLoggable(Level.FINE)) {
+                log.info("closing: context.open=" + context.open.get() + " " + this);
+            }
+
+            context.open.decrementAndGet();
+
+            if (socket != 0 && (status & CLOSED) == 0) {
+                Socket.close(socket);
+                status |= CLOSED;
+            }
+
+            if (handler != null) {
+                if (isBlocking()) {
+                    context.getExecutor().execute(this);
+                } else {
+                    handler.closed(this);
+                }
+            }
+
+            context.destroySocket(this);
+        }
+    }
+
+
+
+    /**
+     * Close input and output, potentially sending RST, than close the socket.
+     *
+     * The proper way to close when gracefully done is by calling writeEnd() and
+     * reading all remaining input until -1 (EOF) is received.
+     *
+     * If EOF is received, the proper way to close is send whatever is remaining and
+     * call writeEnd();
+     */
+    public void reset() {
+        setStatus(ERROR);
+        close();
+    }
+
+
+    /**
+     */
+    public boolean isClosed() {
+        synchronized(this) {
+            if ((status & CLOSED) != 0 || socket == 0 || !context.running) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public long getIOTimeout() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                return Socket.timeoutGet(socket) / 1000;
+            } catch (Exception e) {
+                throw new IOException(e);
+            }
+        } else {
+            throw new IOException("Socket is closed");
+        }
+    }
+
+    // Cert is in DER format
+    // Called after handshake
+    public byte[][] getPeerCert(boolean check) throws IOException {
+        getHost();
+        if (hostInfo.certs != null && hostInfo.certs != NO_CERTS && !check) {
+            return hostInfo.certs;
+        }
+        if (!checkBitAndSocket(SSL_ATTACHED)) {
+            return NO_CERTS;
+        }
+        try {
+            int certLength = SSLSocket.getInfoI(socket,
+                    SSL.SSL_INFO_CLIENT_CERT_CHAIN);
+            // TODO: if resumed, old certs are good.
+            // If not - warn if certs changed, remember first cert, etc.
+            if (certLength <= 0) {
+                // Can also happen on session resume - keep the old
+                if (hostInfo.certs == null) {
+                    hostInfo.certs = NO_CERTS;
+                }
+                return hostInfo.certs;
+            }
+            hostInfo.certs = new byte[certLength + 1][];
+
+            hostInfo.certs[0] = SSLSocket.getInfoB(socket,
+                    SSL.SSL_INFO_CLIENT_CERT);
+            for (int i = 0; i < certLength; i++) {
+                hostInfo.certs[i + 1] = SSLSocket.getInfoB(socket,
+                        SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
+            }
+            return hostInfo.certs;
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public X509Certificate[] getPeerX509Cert() throws IOException {
+        byte[][] certs = getPeerCert(false);
+        X509Certificate[] xcerts = new X509Certificate[certs.length];
+        if (certs.length == 0) {
+            return xcerts;
+        }
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            for (int i = 0; i < certs.length; i++) {
+                if (certs[i] != null) {
+                    ByteArrayInputStream bis = new ByteArrayInputStream(
+                            certs[i]);
+                    xcerts[i] = (X509Certificate) cf.generateCertificate(bis);
+                    bis.close();
+                }
+            }
+        } catch (CertificateException ex) {
+            throw new IOException(ex);
+        }
+        return xcerts;
+    }
+
+    public String getCipherSuite() throws IOException {
+        if (checkBitAndSocket(SSL_ATTACHED)) {
+            return null;
+        }
+        try {
+            return SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public int getKeySize() throws IOException {
+        if (checkBitAndSocket(SSL_ATTACHED)) {
+            return -1;
+        }
+        try {
+            return SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public int getRemotePort() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                Sockaddr addr = Address.getInfo(sa);
+                return addr.port;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getRemoteAddress() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                return Address.getip(sa);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getRemoteHostname() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                String remoteHost = Address.getnameinfo(sa, 0);
+                if (remoteHost == null) {
+                    remoteHost = Address.getip(sa);
+                }
+                return remoteHost;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public int getLocalPort() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                Sockaddr addr = Address.getInfo(sa);
+                return addr.port;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getLocalAddress() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                return Address.getip(sa);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getLocalHostname() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                return Address.getnameinfo(sa, 0);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public boolean isBlocking() {
+        return ! (handler instanceof AprSocketContext.NonBlockingPollHandler);
+    }
+
+    public boolean isError() {
+        return checkPreConnect(ERROR);
+    }
+
+    void notifyError(Throwable err, boolean needsThread) {
+        if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+            if (err != null) {
+                ((AprSocketContext.NonBlockingPollHandler) handler).error(this, err);
+            }
+        } else {
+            // poller destroyed, etc
+            if (needsThread) {
+                context.getExecutor().execute(this);
+            } else {
+                try {
+                    notifyIO();
+                } catch (IOException e) {
+                    log.log(Level.SEVERE, this + " error ", e);
+                }
+            }
+        }
+    }
+
+    // Called after connect and from poller.
+    void notifyIO() throws IOException {
+        long t0 = System.currentTimeMillis();
+        try {
+            if (handler != null) {
+                handler.process(this, true, false, false);
+            }
+        } catch (Throwable t) {
+            throw new IOException(t);
+        } finally {
+            long t1 = System.currentTimeMillis();
+            t1 -= t0;
+            if (t1 > context.maxHandlerTime.get()) {
+                context.maxHandlerTime.set(t1);
+            }
+            context.totalHandlerTime.addAndGet(t1);
+            context.handlerCount.incrementAndGet();
+        }
+    }
+
+    private void notifyConnected(boolean server) throws IOException {
+        // Will set the handler on the channel for accepted
+        context.onSocket(this);
+
+        if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+            ((AprSocketContext.NonBlockingPollHandler) handler).connected(this);
+
+            ((AprSocketContext.NonBlockingPollHandler) handler).process(this, true, true, false);
+            // Now register for polling - unless process() set suspendRead and
+            // doesn't need out notifications
+            updatePolling();
+        } else {
+            if (server) {
+                // client will block in connect().
+                // Server: call process();
+                notifyIO();
+            }
+        }
+    }
+
+    private void updatePolling() throws IOException {
+        synchronized (this) {
+            if ((status & CLOSED) != 0) {
+                maybeDestroy();
+                return;
+            }
+        }
+        context.findPollerAndAdd(this);
+    }
+
+    @Override
+    public void run() {
+        if (!context.running) {
+            return;
+        }
+        if (checkPreConnect(CLOSED)) {
+            if (handler != null) {
+                handler.closed(this);
+            }
+            return;
+        }
+        if (!checkPreConnect(CONNECTED)) {
+            if (checkBitAndSocket(ACCEPTED)) {
+                try {
+                    context.open.incrementAndGet();
+
+                    if (log.isLoggable(Level.FINE)) {
+                        log.info("Accept: " + context.open.get() + " " + this + " " +
+                                getRemotePort());
+                    }
+                    if (context.tcpNoDelay) {
+                        Socket.optSet(socket, Socket.APR_TCP_NODELAY, 1);
+                    }
+
+                    setStatus(CONNECTED);
+                    if (context.sslMode) {
+                        Socket.timeoutSet(socket,
+                                context.connectTimeout * 1000L);
+                        blockingStartTLS();
+                    }
+                    setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+                    notifyConnected(true);
+                    return;
+                } catch (Throwable t) {
+                    t.printStackTrace(); // no error handler yet
+                    reset();
+                    notifyError(t, false);
+                    return;
+                }
+            }
+            if (checkPreConnect(CONNECTING)) {
+                // Non-blocking connect - will call 'afterConnection' at the end.
+                try {
+                    context.connectBlocking(this);
+                } catch (IOException t) {
+                    reset(); // also sets status ERROR
+                    if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+                        ((AprSocketContext.NonBlockingPollHandler) handler).process(this, false, false, true);
+                    }
+                    notifyError(t, false);
+                }
+            }
+        } else {
+            if (handler != null) {
+                try {
+                    notifyIO();
+                } catch (Throwable e) {
+                    log.log(Level.SEVERE, this + " error ", e);
+                    reset();
+                    // no notifyIO - just did it.
+                }
+            }
+        }
+    }
+
+    /**
+     * This is a blocking call ! ( can be made non-blocking, but too complex )
+     *
+     * Will be called automatically after connect() or accept if 'secure' is
+     * true.
+     *
+     * Can be called manually to upgrade the channel
+     * @throws IOException
+     */
+    public void blockingStartTLS() throws IOException {
+        synchronized(this) {
+            if (socket == 0 || !context.running) {
+                return;
+            }
+            if ((status & SSL_ATTACHED) != 0) {
+                return;
+            }
+            status |= SSL_ATTACHED;
+        }
+
+        try {
+            if (log.isLoggable(Level.FINE)) {
+                log.info(this + " StartSSL");
+            }
+
+            AprSocketContext aprCon = context;
+            SSLSocket.attach(aprCon.getSslCtx(), socket);
+
+            if (context.debugSSL) {
+                SSLExt.debug(socket);
+            }
+            if (!getContext().isServer()) {
+                if (context.USE_TICKETS && hostInfo.ticketLen > 0) {
+                    SSLExt.setTicket(socket, hostInfo.ticket,
+                            hostInfo.ticketLen);
+                } else if (hostInfo.sessDer != null) {
+                    SSLExt.setSessionData(socket, hostInfo.sessDer,
+                            hostInfo.sessDer.length);
+                }
+            }
+            SSLExt.sslSetMode(socket, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE |
+                    SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+            int rc = SSLSocket.handshake(socket);
+
+            // At this point we have the session ID, remote certs, etc
+            // we can lookup host info
+            if (hostInfo == null) {
+                hostInfo = new HostInfo();
+            }
+
+            if (rc != Status.APR_SUCCESS) {
+                throw new IOException(this + " Handshake failed " + rc + " "
+                        + Error.strerror(rc) + " SSLL "
+                        + SSL.getLastError());
+            } else { // SUCCESS
+                handshakeDone();
+            }
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void handshakeDone() throws IOException {
+        getHost();
+        if (socket == 0 || !context.running) {
+            throw new IOException("Socket closed");
+        }
+        if (context.USE_TICKETS && ! context.isServer()) {
+            if (hostInfo.ticket == null) {
+                hostInfo.ticket = new byte[2048];
+            }
+            int ticketLen = SSLExt.getTicket(socket, hostInfo.ticket);
+            if (ticketLen > 0) {
+                hostInfo.ticketLen = ticketLen;
+                if (log.isLoggable(Level.FINE)) {
+                    log.info("Received ticket: " + ticketLen);
+                }
+            }
+        }
+
+        // TODO: if the ticket, session id or session changed - callback to
+        // save the session again
+        try {
+            hostInfo.sessDer = SSLExt.getSessionData(socket);
+            getPeerCert(true);
+            hostInfo.sessionId = SSLSocket.getInfoS(socket,
+                    SSL.SSL_INFO_SESSION_ID);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+
+        hostInfo.npn = new byte[32];
+        hostInfo.npnLen = SSLExt.getNPN(socket, hostInfo.npn);
+
+        // If custom verification is used - should check the certificates
+        if (context.tlsCertVerifier != null) {
+            context.tlsCertVerifier.handshakeDone(this);
+        }
+    }
+
+    int requestedPolling() {
+        synchronized(this) {
+            if (socket == 0 || ((status & CLOSED) != 0)) {
+                return 0;
+            }
+            // Implicit:
+            //Poll.APR_POLLNVAL | Poll.APR_POLLHUP | Poll.APR_POLLERR |
+            int res = 0;
+            if ((status & POLLIN) != 0) {
+                res = Poll.APR_POLLIN;
+            }
+            if ((status & POLLOUT) != 0) {
+                res |= Poll.APR_POLLOUT;
+            }
+            return res;
+        }
+    }
+
+    boolean checkBitAndSocket(int bit) {
+        synchronized (this) {
+            return ((status & bit) != 0 && socket != 0 &&
+                    (status & CLOSED) == 0 && context.running);
+        }
+    }
+
+    boolean checkPreConnect(int bit) {
+        synchronized (this) {
+            return ((status & bit) != 0);
+        }
+    }
+
+    void clearStatus(int bit) {
+        synchronized (this) {
+            status &= ~bit;
+        }
+    }
+
+    boolean setStatus(int bit) {
+        synchronized (this) {
+            int old = status & bit;
+            status |= bit;
+            return old != 0;
+        }
+    }
+
+
+}
\ No newline at end of file



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org