You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by jf...@apache.org on 2015/06/18 17:53:35 UTC
svn commit: r1686257 - in /tomcat/native/trunk/native: include/ssl_private.h
src/ssl.c src/sslutils.c
Author: jfclere
Date: Thu Jun 18 15:53:35 2015
New Revision: 1686257
URL: http://svn.apache.org/r1686257
Log:
Add more Twitter and Apple code and fix warnings.
Modified:
tomcat/native/trunk/native/include/ssl_private.h
tomcat/native/trunk/native/src/ssl.c
tomcat/native/trunk/native/src/sslutils.c
Modified: tomcat/native/trunk/native/include/ssl_private.h
URL: http://svn.apache.org/viewvc/tomcat/native/trunk/native/include/ssl_private.h?rev=1686257&r1=1686256&r2=1686257&view=diff
==============================================================================
--- tomcat/native/trunk/native/include/ssl_private.h (original)
+++ tomcat/native/trunk/native/include/ssl_private.h Thu Jun 18 15:53:35 2015
@@ -297,7 +297,7 @@ void SSL_init_app_data2_3_idx(voi
void *SSL_get_app_data2(SSL *);
void SSL_set_app_data2(SSL *, void *);
/* The app_data3 is used to store the handshakeCount pointer for the SSL instance. */
-void *SSL_get_app_data3(SSL *);
+void *SSL_get_app_data3(const SSL *);
void SSL_set_app_data3(SSL *, void *);
int SSL_password_prompt(tcn_pass_cb_t *);
int SSL_password_callback(char *, int, int, void *);
Modified: tomcat/native/trunk/native/src/ssl.c
URL: http://svn.apache.org/viewvc/tomcat/native/trunk/native/src/ssl.c?rev=1686257&r1=1686256&r2=1686257&view=diff
==============================================================================
--- tomcat/native/trunk/native/src/ssl.c (original)
+++ tomcat/native/trunk/native/src/ssl.c Thu Jun 18 15:53:35 2015
@@ -1115,6 +1115,610 @@ TCN_IMPLEMENT_CALL(jboolean, SSL, hasOp)
return op == (op & supported_ssl_opts) ? JNI_TRUE : JNI_FALSE;
}
+/*** Begin Twitter 1:1 API addition ***/
+TCN_IMPLEMENT_CALL(jint, SSL, getLastErrorNumber)(TCN_STDARGS) {
+ UNREFERENCED_STDARGS;
+ return ERR_get_error();
+}
+
+static void ssl_info_callback(const SSL *ssl, int where, int ret) {
+ int *handshakeCount = NULL;
+ if (0 != (where & SSL_CB_HANDSHAKE_START)) {
+ handshakeCount = (int*) SSL_get_app_data3(ssl);
+ if (handshakeCount != NULL) {
+ ++(*handshakeCount);
+ }
+ }
+}
+
+TCN_IMPLEMENT_CALL(jlong /* SSL * */, SSL, newSSL)(TCN_STDARGS,
+ jlong ctx /* tcn_ssl_ctxt_t * */,
+ jboolean server) {
+ tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+ int *handshakeCount = malloc(sizeof(int));
+ SSL *ssl;
+
+ UNREFERENCED_STDARGS;
+
+ TCN_ASSERT(ctx != 0);
+ ssl = SSL_new(c->ctx);
+ if (ssl == NULL) {
+ tcn_ThrowException(e, "cannot create new ssl");
+ return 0;
+ }
+
+ /* Store the handshakeCount in the SSL instance. */
+ *handshakeCount = 0;
+ SSL_set_app_data3(ssl, handshakeCount);
+
+ /* Add callback to keep track of handshakes. */
+ SSL_CTX_set_info_callback(c->ctx, ssl_info_callback);
+
+ if (server) {
+ SSL_set_accept_state(ssl);
+ } else {
+ SSL_set_connect_state(ssl);
+ }
+
+ /* Setup verify and seed */
+ SSL_set_verify_result(ssl, X509_V_OK);
+ SSL_rand_seed(c->rand_file);
+
+ /* Store for later usage in SSL_callback_SSL_verify */
+ SSL_set_app_data2(ssl, c);
+ return P2J(ssl);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setBIO)(TCN_STDARGS,
+ jlong ssl /* SSL * */,
+ jlong rbio /* BIO * */,
+ jlong wbio /* BIO * */) {
+ UNREFERENCED_STDARGS;
+ SSL_set_bio(J2P(ssl, SSL *), J2P(rbio, BIO *), J2P(wbio, BIO *));
+ return;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getError)(TCN_STDARGS,
+ jlong ssl /* SSL * */,
+ jint ret) {
+ UNREFERENCED_STDARGS;
+ return SSL_get_error(J2P(ssl, SSL*), ret);
+}
+
+/* How much did SSL write into this BIO? */
+TCN_IMPLEMENT_CALL(jint /* nbytes */, SSL, pendingWrittenBytesInBIO)(TCN_STDARGS,
+ jlong bio /* BIO * */) {
+ UNREFERENCED_STDARGS;
+
+ return BIO_ctrl_pending(J2P(bio, BIO *));
+}
+
+/* How much is available for reading in the given SSL struct? */
+TCN_IMPLEMENT_CALL(jint, SSL, pendingReadableBytesInSSL)(TCN_STDARGS, jlong ssl /* SSL * */) {
+ UNREFERENCED_STDARGS;
+
+ return SSL_pending(J2P(ssl, SSL *));
+}
+
+/* Write wlen bytes from wbuf into bio */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, writeToBIO)(TCN_STDARGS,
+ jlong bio /* BIO * */,
+ jlong wbuf /* char* */,
+ jint wlen /* sizeof(wbuf) */) {
+ UNREFERENCED_STDARGS;
+
+ return BIO_write(J2P(bio, BIO *), J2P(wbuf, void *), wlen);
+
+}
+
+/* Read up to rlen bytes from bio into rbuf */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, readFromBIO)(TCN_STDARGS,
+ jlong bio /* BIO * */,
+ jlong rbuf /* char * */,
+ jint rlen /* sizeof(rbuf) - 1 */) {
+ UNREFERENCED_STDARGS;
+
+ return BIO_read(J2P(bio, BIO *), J2P(rbuf, void *), rlen);
+}
+
+/* Write up to wlen bytes of application data to the ssl BIO (encrypt) */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, writeToSSL)(TCN_STDARGS,
+ jlong ssl /* SSL * */,
+ jlong wbuf /* char * */,
+ jint wlen /* sizeof(wbuf) */) {
+ UNREFERENCED_STDARGS;
+
+ return SSL_write(J2P(ssl, SSL *), J2P(wbuf, void *), wlen);
+}
+
+/* Read up to rlen bytes of application data from the given SSL BIO (decrypt) */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, readFromSSL)(TCN_STDARGS,
+ jlong ssl /* SSL * */,
+ jlong rbuf /* char * */,
+ jint rlen /* sizeof(rbuf) - 1 */) {
+ UNREFERENCED_STDARGS;
+
+ return SSL_read(J2P(ssl, SSL *), J2P(rbuf, void *), rlen);
+}
+
+/* Get the shutdown status of the engine */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, getShutdown)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ UNREFERENCED_STDARGS;
+
+ return SSL_get_shutdown(J2P(ssl, SSL *));
+}
+
+/* Called when the peer closes the connection */
+TCN_IMPLEMENT_CALL(void, SSL, setShutdown)(TCN_STDARGS,
+ jlong ssl /* SSL * */,
+ jint mode) {
+ UNREFERENCED_STDARGS;
+
+ SSL_set_shutdown(J2P(ssl, SSL *), mode);
+}
+
+/* Free the SSL * and its associated internal BIO */
+TCN_IMPLEMENT_CALL(void, SSL, freeSSL)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ SSL *ssl_ = J2P(ssl, SSL *);
+ int *handshakeCount = SSL_get_app_data3(ssl_);
+
+ UNREFERENCED_STDARGS;
+
+ if (handshakeCount != NULL) {
+ free(handshakeCount);
+ }
+ SSL_free(ssl_);
+}
+
+/* Make a BIO pair (network and internal) for the provided SSL * and return the network BIO */
+TCN_IMPLEMENT_CALL(jlong, SSL, makeNetworkBIO)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ SSL *ssl_ = J2P(ssl, SSL *);
+ BIO *internal_bio;
+ BIO *network_bio;
+
+ UNREFERENCED(o);
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ goto fail;
+ }
+
+ if (BIO_new_bio_pair(&internal_bio, 0, &network_bio, 0) != 1) {
+ tcn_ThrowException(e, "BIO_new_bio_pair failed");
+ goto fail;
+ }
+
+ SSL_set_bio(ssl_, internal_bio, internal_bio);
+
+ return P2J(network_bio);
+ fail:
+ return 0;
+}
+
+/* Free a BIO * (typically, the network BIO) */
+TCN_IMPLEMENT_CALL(void, SSL, freeBIO)(TCN_STDARGS,
+ jlong bio /* BIO * */) {
+ BIO *bio_;
+ UNREFERENCED_STDARGS;
+
+ bio_ = J2P(bio, BIO *);
+ BIO_free(bio_);
+}
+
+/* Send CLOSE_NOTIFY to peer */
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, shutdownSSL)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ UNREFERENCED_STDARGS;
+
+ return SSL_shutdown(J2P(ssl, SSL *));
+}
+
+/* Read which cipher was negotiated for the given SSL *. */
+TCN_IMPLEMENT_CALL(jstring, SSL, getCipherForSSL)(TCN_STDARGS,
+ jlong ssl /* SSL * */)
+{
+ UNREFERENCED_STDARGS;
+
+ return AJP_TO_JSTRING(SSL_get_cipher(J2P(ssl, SSL*)));
+}
+
+/* Read which protocol was negotiated for the given SSL *. */
+TCN_IMPLEMENT_CALL(jstring, SSL, getVersion)(TCN_STDARGS,
+ jlong ssl /* SSL * */)
+{
+ UNREFERENCED_STDARGS;
+
+ return AJP_TO_JSTRING(SSL_get_version(J2P(ssl, SSL*)));
+}
+
+/* Is the handshake over yet? */
+TCN_IMPLEMENT_CALL(jint, SSL, isInInit)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ UNREFERENCED(o);
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return 0;
+ } else {
+ return SSL_in_init(ssl_);
+ }
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, doHandshake)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ SSL *ssl_ = J2P(ssl, SSL *);
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return 0;
+ }
+
+ UNREFERENCED(o);
+
+ return SSL_do_handshake(ssl_);
+}
+
+/* Read which protocol was negotiated for the given SSL *. */
+TCN_IMPLEMENT_CALL(jstring, SSL, getNextProtoNegotiated)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ SSL *ssl_ = J2P(ssl, SSL *);
+ const unsigned char *proto;
+ unsigned int proto_len;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+
+ UNREFERENCED(o);
+
+ SSL_get0_next_proto_negotiated(ssl_, &proto, &proto_len);
+ return tcn_new_stringn(e, proto, proto_len);
+}
+
+/*** End Twitter API Additions ***/
+
+/*** Apple API Additions ***/
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getAlpnSelected)(TCN_STDARGS,
+ jlong ssl /* SSL * */) {
+ /* Looks fishy we have the same in sslnetwork.c, it set by socket/connection */
+ SSL *ssl_ = J2P(ssl, SSL *);
+ const unsigned char *proto;
+ unsigned int proto_len;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+
+ UNREFERENCED(o);
+
+ SSL_get0_alpn_selected(ssl_, &proto, &proto_len);
+ return tcn_new_stringn(e, proto, proto_len);
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getPeerCertChain)(TCN_STDARGS,
+ jlong ssl /* SSL * */)
+{
+ STACK_OF(X509) *sk;
+ int len;
+ int i;
+ X509 *cert;
+ int length;
+ unsigned char *buf;
+ jobjectArray array;
+ jbyteArray bArray;
+
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+
+ UNREFERENCED(o);
+
+ // Get a stack of all certs in the chain.
+ sk = SSL_get_peer_cert_chain(ssl_);
+
+ len = sk_X509_num(sk);
+ if (len <= 0) {
+ /* No peer certificate chain as no auth took place yet, or the auth was not successful. */
+ return NULL;
+ }
+ /* Create the byte[][]Â array that holds all the certs */
+ array = (*e)->NewObjectArray(e, len, byteArrayClass, NULL);
+
+ for(i = 0; i < len; i++) {
+ cert = (X509*) sk_X509_value(sk, i);
+
+ buf = NULL;
+ length = i2d_X509(cert, &buf);
+ if (length < 0) {
+ OPENSSL_free(buf);
+ /* In case of error just return an empty byte[][] */
+ return (*e)->NewObjectArray(e, 0, byteArrayClass, NULL);
+ }
+ bArray = (*e)->NewByteArray(e, length);
+ (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf);
+ (*e)->SetObjectArrayElement(e, array, i, bArray);
+
+ /*
+ * Delete the local reference as we not know how long the chain is and local references are otherwise
+ * only freed once jni method returns.
+ */
+ (*e)->DeleteLocalRef(e, bArray);
+
+ OPENSSL_free(buf);
+ }
+ return array;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getPeerCertificate)(TCN_STDARGS,
+ jlong ssl /* SSL * */)
+{
+ X509 *cert;
+ int length;
+ unsigned char *buf = NULL;
+ jbyteArray bArray;
+
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+
+ UNREFERENCED(o);
+
+ /* Get a stack of all certs in the chain */
+ cert = SSL_get_peer_certificate(ssl_);
+ if (cert == NULL) {
+ return NULL;
+ }
+
+ length = i2d_X509(cert, &buf);
+
+ bArray = (*e)->NewByteArray(e, length);
+ (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf);
+
+ /*
+ * We need to free the cert as the reference count is incremented by one and it is not destroyed when the
+ * session is freed.
+ * See https://www.openssl.org/docs/ssl/SSL_get_peer_certificate.html
+ */
+ X509_free(cert);
+
+ OPENSSL_free(buf);
+
+ return bArray;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getErrorString)(TCN_STDARGS, jlong number)
+{
+ char buf[256];
+ UNREFERENCED(o);
+ ERR_error_string(number, buf);
+ return tcn_new_string(e, buf);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, getTime)(TCN_STDARGS, jlong ssl)
+{
+ const SSL *ssl_ = J2P(ssl, SSL *);
+ const SSL_SESSION *session;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return 0;
+ }
+
+ UNREFERENCED(o);
+
+ session = SSL_get_session(ssl_);
+ if (session) {
+ return SSL_get_time(session);
+ } else {
+ tcn_ThrowException(e, "ssl session is null");
+ return 0;
+ }
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setVerify)(TCN_STDARGS, jlong ssl,
+ jint level, jint depth)
+{
+ tcn_ssl_ctxt_t *c;
+ int verify;
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return;
+ }
+
+ c = SSL_get_app_data2(ssl_);
+
+ verify = SSL_VERIFY_NONE;
+
+ UNREFERENCED(o);
+ TCN_ASSERT(ctx != 0);
+ c->verify_mode = level;
+
+ if (c->verify_mode == SSL_CVERIFY_UNSET)
+ c->verify_mode = SSL_CVERIFY_NONE;
+ if (depth > 0)
+ c->verify_depth = depth;
+ /*
+ * Configure callbacks for SSL context
+ */
+ if (c->verify_mode == SSL_CVERIFY_REQUIRE)
+ verify |= SSL_VERIFY_PEER_STRICT;
+ if ((c->verify_mode == SSL_CVERIFY_OPTIONAL) ||
+ (c->verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ verify |= SSL_VERIFY_PEER;
+ if (!c->store) {
+ if (SSL_CTX_set_default_verify_paths(c->ctx)) {
+ c->store = SSL_CTX_get_cert_store(c->ctx);
+ X509_STORE_set_flags(c->store, 0);
+ }
+ else {
+ /* XXX: See if this is fatal */
+ }
+ }
+
+ SSL_set_verify(ssl_, verify, SSL_callback_SSL_verify);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setOptions)(TCN_STDARGS, jlong ssl,
+ jint opt)
+{
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ UNREFERENCED_STDARGS;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return;
+ }
+
+#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ /* Clear the flag if not supported */
+ if (opt & 0x00040000) {
+ opt &= ~0x00040000;
+ }
+#endif
+ SSL_set_options(ssl_, opt);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getOptions)(TCN_STDARGS, jlong ssl)
+{
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ UNREFERENCED_STDARGS;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return 0;
+ }
+
+ return SSL_get_options(ssl_);
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getCiphers)(TCN_STDARGS, jlong ssl)
+{
+ STACK_OF(SSL_CIPHER) *sk;
+ int len;
+ jobjectArray array;
+ SSL_CIPHER *cipher;
+ const char *name;
+ int i;
+ jstring c_name;
+ SSL *ssl_ = J2P(ssl, SSL *);
+
+ UNREFERENCED_STDARGS;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+
+ sk = SSL_get_ciphers(ssl_);
+ len = sk_SSL_CIPHER_num(sk);
+
+ if (len <= 0) {
+ /* No peer certificate chain as no auth took place yet, or the auth was not successful. */
+ return NULL;
+ }
+
+ /* Create the byte[][]Â array that holds all the certs */
+ array = (*e)->NewObjectArray(e, len, stringClass, NULL);
+
+ for (i = 0; i < len; i++) {
+ cipher = (SSL_CIPHER*) sk_SSL_CIPHER_value(sk, i);
+ name = SSL_CIPHER_get_name(cipher);
+
+ c_name = (*e)->NewStringUTF(e, name);
+ (*e)->SetObjectArrayElement(e, array, i, c_name);
+ }
+ return array;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, setCipherSuites)(TCN_STDARGS, jlong ssl,
+ jstring ciphers)
+{
+ jboolean rv = JNI_TRUE;
+ SSL *ssl_ = J2P(ssl, SSL *);
+ TCN_ALLOC_CSTRING(ciphers);
+
+ UNREFERENCED_STDARGS;
+
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return JNI_FALSE;
+ }
+
+ UNREFERENCED(o);
+ if (!J2S(ciphers)) {
+ return JNI_FALSE;
+ }
+ if (!SSL_set_cipher_list(ssl_, J2S(ciphers))) {
+ char err[256];
+ ERR_error_string(ERR_get_error(), err);
+ tcn_Throw(e, "Unable to configure permitted SSL ciphers (%s)", err);
+ rv = JNI_FALSE;
+ }
+ TCN_FREE_CSTRING(ciphers);
+ return rv;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getSessionId)(TCN_STDARGS, jlong ssl)
+{
+
+ int len;
+ const char *session_id;
+ const SSL_SESSION *session;
+ jbyteArray bArray;
+ SSL *ssl_ = J2P(ssl, SSL *);
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return NULL;
+ }
+ UNREFERENCED(o);
+ session = SSL_get_session(ssl_);
+ session_id = SSL_SESSION_get_id(session, &len);
+
+ if (len == 0 || session_id == NULL) {
+ return NULL;
+ }
+
+ bArray = (*e)->NewByteArray(e, len);
+ (*e)->SetByteArrayRegion(e, bArray, 0, len, (jbyte*) session_id);
+ return bArray;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getHandshakeCount)(TCN_STDARGS, jlong ssl)
+{
+ int *handshakeCount = NULL;
+ SSL *ssl_ = J2P(ssl, SSL *);
+ if (ssl_ == NULL) {
+ tcn_ThrowException(e, "ssl is null");
+ return -1;
+ }
+ UNREFERENCED(o);
+
+ handshakeCount = SSL_get_app_data3(ssl_);
+ if (handshakeCount != NULL) {
+ return *handshakeCount;
+ }
+ return 0;
+}
+
+/*** End Apple API Additions ***/
+
#else
/* OpenSSL is not supported.
* Create empty stubs.
Modified: tomcat/native/trunk/native/src/sslutils.c
URL: http://svn.apache.org/viewvc/tomcat/native/trunk/native/src/sslutils.c?rev=1686257&r1=1686256&r2=1686257&view=diff
==============================================================================
--- tomcat/native/trunk/native/src/sslutils.c (original)
+++ tomcat/native/trunk/native/src/sslutils.c Thu Jun 18 15:53:35 2015
@@ -92,7 +92,7 @@ void SSL_set_app_data2(SSL *ssl, void *a
}
-void *SSL_get_app_data3(SSL *ssl)
+void *SSL_get_app_data3(const SSL *ssl)
{
return SSL_get_ex_data(ssl, SSL_app_data3_idx);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org