You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by gs...@apache.org on 2014/11/26 21:06:01 UTC

[14/35] qpid-proton git commit: PROTON-749: Server transport autodetect: - Defer layer initialisation until first send/receive - Move layer initialisation entirely to transport - Server transports will now autodetect the client protocol layers talking

PROTON-749: Server transport autodetect:
- Defer layer initialisation until first send/receive
- Move layer initialisation entirely to transport
- Server transports will now autodetect the client protocol layers
  talking to them and configure themselves automatically
- There are still some hacks in here to make SSL work.
  It's not obvious why we need these hacks.


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/1b2be03c
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/1b2be03c
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/1b2be03c

Branch: refs/heads/examples
Commit: 1b2be03c748ef5a57cf181f8373b9b6e8f8cfd22
Parents: 9c9872b
Author: Andrew Stitcher <as...@apache.org>
Authored: Wed Aug 20 23:39:34 2014 -0400
Committer: Andrew Stitcher <as...@apache.org>
Committed: Wed Nov 19 17:50:21 2014 -0500

----------------------------------------------------------------------
 proton-c/CMakeLists.txt               |   1 +
 proton-c/src/engine/engine-internal.h |   8 +-
 proton-c/src/sasl/sasl-internal.h     |   2 +
 proton-c/src/sasl/sasl.c              |  55 +++----
 proton-c/src/ssl/openssl.c            | 112 +------------
 proton-c/src/ssl/ssl-internal.h       |   2 +
 proton-c/src/ssl/ssl_stub.c           |  17 +-
 proton-c/src/transport/autodetect.c   | 135 ++++++++++++++++
 proton-c/src/transport/autodetect.h   |  40 +++++
 proton-c/src/transport/transport.c    | 249 +++++++++++++++++++++++------
 proton-c/src/windows/schannel.c       | 113 +------------
 11 files changed, 428 insertions(+), 306 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/proton-c/CMakeLists.txt b/proton-c/CMakeLists.txt
index 6b6b730..b09e1c4 100644
--- a/proton-c/CMakeLists.txt
+++ b/proton-c/CMakeLists.txt
@@ -297,6 +297,7 @@ set (qpid-proton-core
   src/dispatcher/dispatcher.c
   src/engine/engine.c
   src/events/event.c
+  src/transport/autodetect.c
   src/transport/transport.c
   src/message/message.c
   src/sasl/sasl.c

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/engine/engine-internal.h
----------------------------------------------------------------------
diff --git a/proton-c/src/engine/engine-internal.h b/proton-c/src/engine/engine-internal.h
index ab66ef5..f53e88b 100644
--- a/proton-c/src/engine/engine-internal.h
+++ b/proton-c/src/engine/engine-internal.h
@@ -107,6 +107,9 @@ typedef struct pn_io_layer_t {
 } pn_io_layer_t;
 
 extern const pn_io_layer_t pni_passthru_layer;
+extern const pn_io_layer_t ssl_layer;
+extern const pn_io_layer_t sasl_header_layer;
+extern const pn_io_layer_t sasl_write_header_layer;
 
 typedef struct pni_sasl_t pni_sasl_t;
 typedef struct pni_ssl_t pni_ssl_t;
@@ -131,10 +134,7 @@ struct pn_transport_t {
   pn_condition_t condition;
   pn_error_t *error;
 
-#define PN_IO_SSL  0
-#define PN_IO_SASL 1
-#define PN_IO_AMQP 2
-#define PN_IO_LAYER_CT (PN_IO_AMQP+1)
+#define PN_IO_LAYER_CT 3
   const pn_io_layer_t *io_layers[PN_IO_LAYER_CT];
 
   /* dead remote detection */

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/sasl/sasl-internal.h
----------------------------------------------------------------------
diff --git a/proton-c/src/sasl/sasl-internal.h b/proton-c/src/sasl/sasl-internal.h
index 15fd0b1..ca4c80e 100644
--- a/proton-c/src/sasl/sasl-internal.h
+++ b/proton-c/src/sasl/sasl-internal.h
@@ -57,4 +57,6 @@ void pn_sasl_trace(pn_transport_t *transport, pn_trace_t trace);
  */
 void pn_sasl_free(pn_transport_t *transport);
 
+bool pn_sasl_skipping_allowed(pn_transport_t *transport);
+
 #endif /* sasl-internal.h */

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/sasl/sasl.c
----------------------------------------------------------------------
diff --git a/proton-c/src/sasl/sasl.c b/proton-c/src/sasl/sasl.c
index 90adcf6..1ee8f9b 100644
--- a/proton-c/src/sasl/sasl.c
+++ b/proton-c/src/sasl/sasl.c
@@ -32,6 +32,7 @@
 #include "engine/engine-internal.h"
 #include "dispatcher/dispatcher.h"
 #include "util.h"
+#include "transport/autodetect.h"
 
 
 struct pni_sasl_t {
@@ -68,7 +69,7 @@ static ssize_t pn_input_read_sasl(pn_transport_t *transport, unsigned int layer,
 static ssize_t pn_output_write_sasl_header(pn_transport_t* transport, unsigned int layer, char* bytes, size_t size);
 static ssize_t pn_output_write_sasl(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available);
 
-const pn_io_layer_t sasl_headers_layer = {
+const pn_io_layer_t sasl_header_layer = {
     pn_input_read_sasl_header,
     pn_output_write_sasl_header,
     NULL,
@@ -118,7 +119,6 @@ pn_sasl_t *pn_sasl(pn_transport_t *transport)
     sasl->output_bypass = false;
 
     transport->sasl = sasl;
-    transport->io_layers[PN_IO_SASL] = &sasl_headers_layer;
   }
 
   // The actual external pn_sasl_t pointer is a pointer to its enclosing pn_transport_t
@@ -204,6 +204,11 @@ void pn_sasl_allow_skip(pn_sasl_t *sasl0, bool allow)
     sasl->allow_skip = allow;
 }
 
+bool pn_sasl_skipping_allowed(pn_transport_t *transport)
+{
+  return transport && transport->sasl && transport->sasl->allow_skip;
+}
+
 void pn_sasl_plain(pn_sasl_t *sasl0, const char *username, const char *password)
 {
   pni_sasl_t *sasl = get_sasl_internal(sasl0);
@@ -441,45 +446,33 @@ int pn_do_outcome(pn_dispatcher_t *disp)
 }
 
 #define SASL_HEADER ("AMQP\x03\x01\x00\x00")
-#define AMQP_HEADER ("AMQP\x00\x01\x00\x00")
 #define SASL_HEADER_LEN 8
 
 static ssize_t pn_input_read_sasl_header(pn_transport_t* transport, unsigned int layer, const char* bytes, size_t available)
 {
-  pni_sasl_t *sasl = transport->sasl;
-  if (available > 0) {
-    if (available < SASL_HEADER_LEN) {
-      if (memcmp(bytes, SASL_HEADER, available) == 0 ||
-          memcmp(bytes, AMQP_HEADER, available) == 0)
-        return 0;
+  bool eos = pn_transport_capacity(transport)==PN_EOS;
+  pni_protocol_type_t protocol = pni_sniff_header(bytes, available);
+  switch (protocol) {
+  case PNI_PROTOCOL_AMQP_SASL:
+    if (transport->io_layers[layer] == &sasl_read_header_layer) {
+        transport->io_layers[layer] = &sasl_layer;
     } else {
-      if (memcmp(bytes, SASL_HEADER, SASL_HEADER_LEN) == 0) {
-        if (transport->io_layers[layer] == &sasl_read_header_layer) {
-          transport->io_layers[layer] = &sasl_layer;
-        } else {
-          transport->io_layers[layer] = &sasl_write_header_layer;
-        }
-        if (sasl->disp->trace & PN_TRACE_FRM)
-          pn_transport_logf(transport, "  <- %s", "SASL");
-        return SASL_HEADER_LEN;
-      }
-      if (memcmp(bytes, AMQP_HEADER, SASL_HEADER_LEN) == 0) {
-        if (sasl->allow_skip) {
-          sasl->outcome = PN_SASL_SKIPPED;
-          transport->io_layers[layer] = &pni_passthru_layer;
-          return pni_passthru_layer.process_input(transport, layer, bytes, available);
-        } else {
-            pn_do_error(transport, "amqp:connection:policy-error",
-                        "Client skipped SASL exchange - forbidden");
-            return PN_EOS;
-        }
-      }
+        transport->io_layers[layer] = &sasl_write_header_layer;
     }
+    if (transport->sasl->disp->trace & PN_TRACE_FRM)
+        pn_transport_logf(transport, "  <- %s", "SASL");
+    return SASL_HEADER_LEN;
+  case PNI_PROTOCOL_INSUFFICIENT:
+    if (!eos) return 0;
+    /* Fallthru */
+  default:
+    break;
   }
   char quoted[1024];
   pn_quote_data(quoted, 1024, bytes, available);
   pn_do_error(transport, "amqp:connection:framing-error",
-              "%s header mismatch: '%s'", "SASL", quoted);
+              "%s header mismatch: %s ['%s']%s", "SASL", pni_protocol_name(protocol), quoted,
+              !eos ? "" : " (connection aborted)");
   return PN_EOS;
 }
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/ssl/openssl.c
----------------------------------------------------------------------
diff --git a/proton-c/src/ssl/openssl.c b/proton-c/src/ssl/openssl.c
index 41e36b5..0562cae 100644
--- a/proton-c/src/ssl/openssl.c
+++ b/proton-c/src/ssl/openssl.c
@@ -60,7 +60,6 @@
 static int ssl_initialized;
 static int ssl_ex_data_index;
 
-typedef enum { UNKNOWN_CONNECTION, SSL_CONNECTION, CLEAR_CONNECTION } connection_mode_t;
 typedef struct pn_ssl_session_t pn_ssl_session_t;
 
 struct pn_ssl_domain_t {
@@ -145,11 +144,8 @@ struct pn_ssl_session_t {
 static int keyfile_pw_cb(char *buf, int size, int rwflag, void *userdata);
 static ssize_t process_input_ssl( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
 static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
-static ssize_t process_input_unknown( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
-static ssize_t process_output_unknown( pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
 static ssize_t process_input_done(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
 static ssize_t process_output_done(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
-static connection_mode_t check_for_ssl_connection( const char *data, size_t len );
 static int init_ssl_socket(pn_transport_t *, pni_ssl_t *);
 static void release_ssl_socket( pni_ssl_t * );
 static pn_ssl_session_t *ssn_cache_find( pn_ssl_domain_t *, const char * );
@@ -682,13 +678,6 @@ int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain,
   return 0;
 }
 
-const pn_io_layer_t unknown_layer = {
-    process_input_unknown,
-    process_output_unknown,
-    NULL,
-    NULL
-};
-
 const pn_io_layer_t ssl_layer = {
     process_input_ssl,
     process_output_ssl,
@@ -725,11 +714,6 @@ int pn_ssl_init(pn_ssl_t *ssl0, pn_ssl_domain_t *domain, const char *session_id)
 
   ssl->domain = domain;
   domain->ref_count++;
-  if (domain->allow_unsecured) {
-    transport->io_layers[PN_IO_SSL] = &unknown_layer;
-  } else {
-    transport->io_layers[PN_IO_SSL] = &ssl_layer;
-  }
   if (session_id && domain->mode == PN_SSL_MODE_CLIENT)
     ssl->session_id = pn_strdup(session_id);
 
@@ -748,6 +732,10 @@ int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain)
   return 0;
 }
 
+bool pn_ssl_allow_unsecured(pn_transport_t *transport)
+{
+  return transport && transport->ssl && transport->ssl->domain && transport->ssl->domain->allow_unsecured;
+}
 
 bool pn_ssl_get_cipher_name(pn_ssl_t *ssl0, char *buffer, size_t size )
 {
@@ -862,13 +850,6 @@ static int start_ssl_shutdown(pn_transport_t *transport)
 }
 
 
-
-static int setup_ssl_connection(pn_transport_t *transport, unsigned int layer)
-{
-  transport->io_layers[layer] = &ssl_layer;
-  return 0;
-}
-
 //////// SSL Connections
 
 
@@ -1213,91 +1194,6 @@ static void release_ssl_socket(pni_ssl_t *ssl)
 }
 
 
-static int setup_cleartext_connection(pn_transport_t *transport, unsigned int layer)
-{
-  transport->io_layers[layer] = &pni_passthru_layer;
-  return 0;
-}
-
-
-// until we determine if the client is using SSL or not:
-
-static ssize_t process_input_unknown(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len)
-{
-  switch (check_for_ssl_connection( input_data, len )) {
-  case SSL_CONNECTION:
-    ssl_log( transport, "SSL connection detected.");
-    setup_ssl_connection(transport, layer);
-    break;
-  case CLEAR_CONNECTION:
-    ssl_log( transport, "Cleartext connection detected.");
-    setup_cleartext_connection(transport, layer);
-    break;
-  default:
-    return 0;
-  }
-  return transport->io_layers[layer]->process_input(transport, layer, input_data, len );
-}
-
-static ssize_t process_output_unknown(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len)
-{
-  // do not do output until we know if SSL is used or not
-  return 0;
-}
-
-static connection_mode_t check_for_ssl_connection( const char *data, size_t len )
-{
-  if (len >= 5) {
-    const unsigned char *buf = (unsigned char *)data;
-    /*
-     * SSLv2 Client Hello format
-     * http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html
-     *
-     * Bytes 0-1: RECORD-LENGTH
-     * Byte    2: MSG-CLIENT-HELLO (1)
-     * Byte    3: CLIENT-VERSION-MSB
-     * Byte    4: CLIENT-VERSION-LSB
-     *
-     * Allowed versions:
-     * 2.0 - SSLv2
-     * 3.0 - SSLv3
-     * 3.1 - TLS 1.0
-     * 3.2 - TLS 1.1
-     * 3.3 - TLS 1.2
-     *
-     * The version sent in the Client-Hello is the latest version supported by
-     * the client. NSS may send version 3.x in an SSLv2 header for
-     * maximum compatibility.
-     */
-    int isSSL2Handshake = buf[2] == 1 &&   // MSG-CLIENT-HELLO
-      ((buf[3] == 3 && buf[4] <= 3) ||    // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
-       (buf[3] == 2 && buf[4] == 0));     // SSL 2
-
-    /*
-     * SSLv3/TLS Client Hello format
-     * RFC 2246
-     *
-     * Byte    0: ContentType (handshake - 22)
-     * Bytes 1-2: ProtocolVersion {major, minor}
-     *
-     * Allowed versions:
-     * 3.0 - SSLv3
-     * 3.1 - TLS 1.0
-     * 3.2 - TLS 1.1
-     * 3.3 - TLS 1.2
-     */
-    int isSSL3Handshake = buf[0] == 22 &&  // handshake
-      (buf[1] == 3 && buf[2] <= 3);       // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
-
-    if (isSSL2Handshake || isSSL3Handshake) {
-      return SSL_CONNECTION;
-    } else {
-      return CLEAR_CONNECTION;
-    }
-  }
-  return UNKNOWN_CONNECTION;
-}
-
 void pn_ssl_trace(pn_transport_t *transport, pn_trace_t trace)
 {
   transport->ssl->trace = trace;

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/ssl/ssl-internal.h
----------------------------------------------------------------------
diff --git a/proton-c/src/ssl/ssl-internal.h b/proton-c/src/ssl/ssl-internal.h
index f1cd637..9430af0 100644
--- a/proton-c/src/ssl/ssl-internal.h
+++ b/proton-c/src/ssl/ssl-internal.h
@@ -33,4 +33,6 @@ void pn_ssl_free(pn_transport_t *transport);
 
 void pn_ssl_trace(pn_transport_t *transport, pn_trace_t trace);
 
+bool pn_ssl_allow_unsecured(pn_transport_t *transport);
+
 #endif /* ssl-internal.h */

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/ssl/ssl_stub.c
----------------------------------------------------------------------
diff --git a/proton-c/src/ssl/ssl_stub.c b/proton-c/src/ssl/ssl_stub.c
index a5fce02..cea5dc4 100644
--- a/proton-c/src/ssl/ssl_stub.c
+++ b/proton-c/src/ssl/ssl_stub.c
@@ -22,6 +22,7 @@
 #include <proton/ssl.h>
 #include <proton/error.h>
 #include <proton/transport.h>
+#include "engine/engine-internal.h"
 
 
 /** @file
@@ -55,16 +56,23 @@ void pn_ssl_trace(pn_ssl_t *ssl, pn_trace_t trace)
 {
 }
 
-ssize_t pn_ssl_input(pn_ssl_t *ssl, const char *bytes, size_t available)
+ssize_t pn_ssl_input(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available)
 {
   return PN_EOS;
 }
 
-ssize_t pn_ssl_output(pn_ssl_t *ssl, char *buffer, size_t max_size)
+ssize_t pn_ssl_output(pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_size)
 {
   return PN_EOS;
 }
 
+const pn_io_layer_t ssl_layer = {
+    pn_ssl_input,
+    pn_ssl_output,
+    NULL,
+    NULL
+};
+
 bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size)
 {
   return false;
@@ -110,6 +118,11 @@ int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain)
   return -1;
 }
 
+bool pn_ssl_allow_unsecured(pn_ssl_t *ssl)
+{
+  return true;
+}
+
 pn_ssl_resume_status_t pn_ssl_resume_status( pn_ssl_t *s )
 {
   return PN_SSL_RESUME_UNKNOWN;

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/transport/autodetect.c
----------------------------------------------------------------------
diff --git a/proton-c/src/transport/autodetect.c b/proton-c/src/transport/autodetect.c
new file mode 100644
index 0000000..00f6d98
--- /dev/null
+++ b/proton-c/src/transport/autodetect.c
@@ -0,0 +1,135 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "autodetect.h"
+
+#define SASL_HEADER ("AMQP\x03\x01\x00\x00")
+#define SSL_HEADER  ("AMQP\x02\x01\x00\x00")
+#define AMQP_HEADER ("AMQP\x00\x01\x00\x00")
+
+#define SASL_HEADER_LEN 8
+
+/*
+ * SSLv2 Client Hello format
+ * http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html
+ *
+ * Bytes 0-1: RECORD-LENGTH
+ * Byte    2: MSG-CLIENT-HELLO (1)
+ * Byte    3: CLIENT-VERSION-MSB
+ * Byte    4: CLIENT-VERSION-LSB
+ *
+ * Allowed versions:
+ * 2.0 - SSLv2
+ * 3.0 - SSLv3
+ * 3.1 - TLS 1.0
+ * 3.2 - TLS 1.1
+ * 3.3 - TLS 1.2
+ *
+ * The version sent in the Client-Hello is the latest version supported by
+ * the client. NSS may send version 3.x in an SSLv2 header for
+ * maximum compatibility.
+ */
+/*
+ * SSLv3/TLS Client Hello format
+ * RFC 2246
+ *
+ * Byte    0: ContentType (handshake - 22)
+ * Bytes 1-2: ProtocolVersion {major, minor}
+ *
+ * Allowed versions:
+ * 3.0 - SSLv3
+ * 3.1 - TLS 1.0
+ * 3.2 - TLS 1.1
+ * 3.3 - TLS 1.2
+ */
+/*
+ * AMQP 1.0 Header
+ *
+ * Bytes 0-3: "AMQP"
+ * Byte    4: 0==AMQP, 2==SSL, 3==SASL
+ * Byte    5: 1
+ * Bytes 6-7: 0
+ */
+/*
+ * AMQP Pre 1.0 Header
+ *
+ * Bytes 0-3: 'AMQP'
+ * Byte    4: 1
+ * Byte    5: 1
+ * Byte    6: 0 (major version)
+ * Byte    7: Minor version
+ */
+pni_protocol_type_t pni_sniff_header(const char *buf, size_t len)
+{
+  if (len < 3) return PNI_PROTOCOL_INSUFFICIENT;
+  bool isSSL3Handshake = buf[0]==22 &&            // handshake
+                         buf[1]==3  && buf[2]<=3; // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
+  if (isSSL3Handshake) return PNI_PROTOCOL_SSL;
+
+  bool isFirst3AMQP = buf[0]=='A' && buf[1]=='M' && buf[2]=='Q';
+  bool isFirst3SSL2CLientHello = buf[2]==1;       // Client Hello
+  if (!isFirst3AMQP && !isFirst3SSL2CLientHello) return PNI_PROTOCOL_UNKNOWN;
+
+
+  if (len < 4) return PNI_PROTOCOL_INSUFFICIENT;
+  bool isAMQP = isFirst3AMQP && buf[3]=='P';
+  bool isFirst4SSL2ClientHello = isFirst3SSL2CLientHello && (buf[3]==2 || buf[3]==3);
+  if (!isAMQP && !isFirst4SSL2ClientHello) return PNI_PROTOCOL_UNKNOWN;
+
+  if (len < 5) return PNI_PROTOCOL_INSUFFICIENT;
+  bool isSSL2Handshake = buf[2] == 1 &&   // MSG-CLIENT-HELLO
+      ((buf[3] == 3 && buf[4] <= 3) ||    // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
+       (buf[3] == 2 && buf[4] == 0));     // SSL 2
+  if (isSSL2Handshake) return PNI_PROTOCOL_SSL;
+
+  bool isFirst5OldAMQP = isAMQP && buf[4]==1;
+  bool isFirst5AMQP = isAMQP && (buf[4]==0 || buf[4]==2 || buf[4]==3);
+  if (!isFirst5AMQP && !isFirst5OldAMQP) return PNI_PROTOCOL_UNKNOWN;
+
+  if (len < 6) return PNI_PROTOCOL_INSUFFICIENT;
+
+  // Both old and new versions of AMQP have 1 in byte 5
+  if (buf[5]!=1) return PNI_PROTOCOL_UNKNOWN;
+
+  // From here on it must be some sort of AMQP
+  if (len < 8) return PNI_PROTOCOL_INSUFFICIENT;
+  if (buf[6]==0 && buf[7]==0) {
+    // AM<QP 1.0
+      if (buf[4]==0) return PNI_PROTOCOL_AMQP1;
+      if (buf[4]==2) return PNI_PROTOCOL_AMQP_SSL;
+      if (buf[4]==3) return PNI_PROTOCOL_AMQP_SASL;
+  }
+  return PNI_PROTOCOL_AMQP_OTHER;
+}
+
+const char* pni_protocol_name(pni_protocol_type_t p)
+{
+  static const char* names[] = {
+  "Insufficient data to determine protocol",
+  "Unknown protocol",
+  "SSL/TLS connection",
+  "AMQP TLS layer",
+  "AMQP SASL layer",
+  "AMQP 1.0 layer",
+  "Pre standard AMQP connection"
+  };
+  return names[p];
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/transport/autodetect.h
----------------------------------------------------------------------
diff --git a/proton-c/src/transport/autodetect.h b/proton-c/src/transport/autodetect.h
new file mode 100644
index 0000000..12cb7d8
--- /dev/null
+++ b/proton-c/src/transport/autodetect.h
@@ -0,0 +1,40 @@
+#ifndef PROTON_AUTODETECT_H
+#define PROTON_AUTODETECT_H 1
+
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "proton/types.h"
+
+typedef enum {
+  PNI_PROTOCOL_INSUFFICIENT,
+  PNI_PROTOCOL_UNKNOWN,
+  PNI_PROTOCOL_SSL,
+  PNI_PROTOCOL_AMQP_SSL,
+  PNI_PROTOCOL_AMQP_SASL,
+  PNI_PROTOCOL_AMQP1,
+  PNI_PROTOCOL_AMQP_OTHER
+} pni_protocol_type_t;
+
+pni_protocol_type_t pni_sniff_header(const char *data, size_t len);
+const char* pni_protocol_name(pni_protocol_type_t p);
+
+#endif

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/transport/transport.c
----------------------------------------------------------------------
diff --git a/proton-c/src/transport/transport.c b/proton-c/src/transport/transport.c
index 2d5f93a..67fd3ab 100644
--- a/proton-c/src/transport/transport.c
+++ b/proton-c/src/transport/transport.c
@@ -19,22 +19,25 @@
  *
  */
 
+#include "proton/framing.h"
+
 #include "engine/engine-internal.h"
-#include <stdlib.h>
-#include <string.h>
-#include <proton/framing.h>
+#include "sasl/sasl-internal.h"
+#include "ssl/ssl-internal.h"
+
+#include "autodetect.h"
 #include "protocol.h"
 #include "dispatch_actions.h"
+#include "proton/event.h"
+#include "platform.h"
+#include "platform_fmt.h"
 
+#include <stdlib.h>
+#include <string.h>
 #include <assert.h>
 #include <stdarg.h>
 #include <stdio.h>
 
-#include "sasl/sasl-internal.h"
-#include "ssl/ssl-internal.h"
-#include "platform.h"
-#include "platform_fmt.h"
-
 static ssize_t transport_consume(pn_transport_t *transport);
 
 // delivery buffers
@@ -92,26 +95,25 @@ void pn_delivery_map_clear(pn_delivery_map_t *dm)
   dm->next = 0;
 }
 
+static void pni_default_tracer(pn_transport_t *transport, const char *message)
+{
+  fprintf(stderr, "[%p]:%s\n", (void *) transport, message);
+}
+
 static ssize_t pn_io_layer_input_passthru(pn_transport_t *, unsigned int, const char *, size_t );
 static ssize_t pn_io_layer_output_passthru(pn_transport_t *, unsigned int, char *, size_t );
 
+static ssize_t pn_io_layer_input_setup(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available);
+static ssize_t pn_io_layer_output_setup(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available);
+
 static ssize_t pn_input_read_amqp_header(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available);
 static ssize_t pn_input_read_amqp(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available);
 static ssize_t pn_output_write_amqp_header(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available);
 static ssize_t pn_output_write_amqp(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available);
 static pn_timestamp_t pn_tick_amqp(pn_transport_t *transport, unsigned int layer, pn_timestamp_t now);
 
-static void pni_default_tracer(pn_transport_t *transport, const char *message)
-{
-  fprintf(stderr, "[%p]:%s\n", (void *) transport, message);
-}
-
-const pn_io_layer_t pni_passthru_layer = {
-    pn_io_layer_input_passthru,
-    pn_io_layer_output_passthru,
-    NULL,
-    NULL
-};
+static ssize_t pn_io_layer_input_autodetect(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available);
+static ssize_t pn_io_layer_output_null(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available);
 
 const pn_io_layer_t amqp_header_layer = {
     pn_input_read_amqp_header,
@@ -141,6 +143,158 @@ const pn_io_layer_t amqp_layer = {
     NULL
 };
 
+const pn_io_layer_t pni_setup_layer = {
+    pn_io_layer_input_setup,
+    pn_io_layer_output_setup,
+    NULL,
+    NULL
+};
+
+const pn_io_layer_t pni_autodetect_layer = {
+    pn_io_layer_input_autodetect,
+    pn_io_layer_output_null,
+    NULL,
+    NULL
+};
+
+const pn_io_layer_t pni_passthru_layer = {
+    pn_io_layer_input_passthru,
+    pn_io_layer_output_passthru,
+    NULL,
+    NULL
+};
+
+/* Set up the transport protocol layers depending on what is configured */
+static void pn_io_layer_setup(pn_transport_t *transport, unsigned int layer)
+{
+  assert(layer == 0);
+  // Figure out if we are server or not
+  if (transport->server)
+  {
+    // XXX: This is currently a large hack to work around the SSL
+    // code not handling a connection error before being set up fully
+    if (transport->ssl && pn_ssl_allow_unsecured(transport)) {
+      transport->io_layers[layer++] = &pni_autodetect_layer;
+      return;
+    }
+  }
+  if (transport->ssl) {
+    transport->io_layers[layer++] = &ssl_layer;
+  }
+  if (transport->server) {
+    transport->io_layers[layer++] = &pni_autodetect_layer;
+    return;
+  }
+  if (transport->sasl) {
+    transport->io_layers[layer++] = &sasl_header_layer;
+  }
+  transport->io_layers[layer++] = &amqp_header_layer;
+}
+
+ssize_t pn_io_layer_input_setup(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available)
+{
+  pn_io_layer_setup(transport, layer);
+  return transport->io_layers[layer]->process_input(transport, layer, bytes, available);
+}
+
+ssize_t pn_io_layer_output_setup(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available)
+{
+  pn_io_layer_setup(transport, layer);
+  return transport->io_layers[layer]->process_output(transport, layer, bytes, available);
+}
+
+// Autodetect the layer by reading the protocol header
+ssize_t pn_io_layer_input_autodetect(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available)
+{
+  const char* error;
+  bool eos = pn_transport_capacity(transport)==PN_EOS;
+  if (eos && available==0) {
+    pn_do_error(transport, "amqp:connection:framing-error", "No valid protocol header found");
+    return PN_EOS;
+  }
+  pni_protocol_type_t protocol = pni_sniff_header(bytes, available);
+  if (transport->disp->trace & PN_TRACE_DRV)
+      pn_transport_logf(transport, "%s detected", pni_protocol_name(protocol));
+  switch (protocol) {
+  case PNI_PROTOCOL_SSL:
+    if (!transport->ssl) {
+      pn_ssl(transport);
+    }
+    transport->io_layers[layer] = &ssl_layer;
+    transport->io_layers[layer+1] = &pni_autodetect_layer;
+    return ssl_layer.process_input(transport, layer, bytes, available);
+  case PNI_PROTOCOL_AMQP_SSL:
+    if (!transport->ssl) {
+      pn_ssl(transport);
+    }
+    transport->io_layers[layer] = &ssl_layer;
+    transport->io_layers[layer+1] = &pni_autodetect_layer;
+    return 8;
+  case PNI_PROTOCOL_AMQP_SASL:
+    if (!transport->sasl) {
+      pn_sasl(transport);
+    }
+    transport->io_layers[layer] = &sasl_write_header_layer;
+    transport->io_layers[layer+1] = &pni_autodetect_layer;
+    if (transport->disp->trace & PN_TRACE_FRM)
+        pn_transport_logf(transport, "  <- %s", "SASL");
+    return 8;
+  case PNI_PROTOCOL_AMQP1:
+    if (transport->sasl && pn_sasl_state((pn_sasl_t *)transport)==PN_SASL_IDLE) {
+      if (pn_sasl_skipping_allowed(transport)) {
+        pn_sasl_done((pn_sasl_t *)transport, PN_SASL_SKIPPED);
+      } else {
+        pn_do_error(transport, "amqp:connection:policy-error",
+                    "Client skipped SASL exchange - forbidden");
+        return PN_EOS;
+      }
+    }
+    transport->io_layers[layer] = &amqp_write_header_layer;
+    if (transport->disp->trace & PN_TRACE_FRM)
+        pn_transport_logf(transport, "  <- %s", "AMQP");
+    return 8;
+  case PNI_PROTOCOL_INSUFFICIENT:
+    if (!eos) return 0;
+    error = "End of input stream before protocol detection";
+    break;
+  case PNI_PROTOCOL_AMQP_OTHER:
+    error = "Incompatible AMQP connection detected";
+    break;
+  case PNI_PROTOCOL_UNKNOWN:
+  default:
+    error = "Unknown protocol detected";
+    break;
+  }
+  char quoted[1024];
+  pn_quote_data(quoted, 1024, bytes, available);
+  pn_do_error(transport, "amqp:connection:framing-error",
+              "%s: '%s'%s", error, quoted,
+              !eos ? "" : " (connection aborted)");
+  return PN_EOS;
+}
+
+// We don't know what the output should be - do nothing
+ssize_t pn_io_layer_output_null(pn_transport_t *transport, unsigned int layer, char *bytes, size_t available)
+{
+  return 0;
+}
+
+/** Pass through input handler */
+ssize_t pn_io_layer_input_passthru(pn_transport_t *transport, unsigned int layer, const char *data, size_t available)
+{
+    if (layer+1<PN_IO_LAYER_CT)
+        return transport->io_layers[layer+1]->process_input(transport, layer+1, data, available);
+    return PN_EOS;
+}
+
+/** Pass through output handler */
+ssize_t pn_io_layer_output_passthru(pn_transport_t *transport, unsigned int layer, char *data, size_t available)
+{
+    if (layer+1<PN_IO_LAYER_CT)
+        return transport->io_layers[layer+1]->process_output(transport, layer+1, data, available);
+    return PN_EOS;
+}
+
 static void pn_transport_initialize(void *object)
 {
   pn_transport_t *transport = (pn_transport_t *)object;
@@ -157,9 +311,11 @@ static void pn_transport_initialize(void *object)
   transport->connection = NULL;
 
   for (int layer=0; layer<PN_IO_LAYER_CT; ++layer) {
-    transport->io_layers[layer] = &pni_passthru_layer;
+    transport->io_layers[layer] = NULL;
   }
-  transport->io_layers[PN_IO_AMQP] = &amqp_header_layer;
+
+  // Defer setting up the layers until the first data arrives or is sent
+  transport->io_layers[0] = &pni_setup_layer;
 
   transport->open_sent = false;
   transport->open_rcvd = false;
@@ -1127,20 +1283,12 @@ static ssize_t transport_consume(pn_transport_t *transport)
   return consumed;
 }
 
-#define AMQP_HEADER ("AMQP\x00\x01\x00\x00")
-
 static ssize_t pn_input_read_amqp_header(pn_transport_t* transport, unsigned int layer, const char* bytes, size_t available)
 {
-  unsigned readable = pn_min(8, available);
   bool eos = pn_transport_capacity(transport)==PN_EOS;
-  if (memcmp(bytes, AMQP_HEADER, readable) || (readable<8 && eos) ) {
-    char quoted[1024];
-    pn_quote_data(quoted, 1024, bytes, available);
-    pn_do_error(transport, "amqp:connection:framing-error",
-                "%s header mismatch: '%s'%s", "AMQP", quoted,
-                !eos ? "" : " (connection aborted)");
-    return PN_EOS;
-  } else if (readable==8) {
+  pni_protocol_type_t protocol = pni_sniff_header(bytes, available);
+  switch (protocol) {
+  case PNI_PROTOCOL_AMQP1:
     if (transport->io_layers[layer] == &amqp_read_header_layer) {
       transport->io_layers[layer] = &amqp_layer;
     } else {
@@ -1149,8 +1297,18 @@ static ssize_t pn_input_read_amqp_header(pn_transport_t* transport, unsigned int
     if (transport->disp->trace & PN_TRACE_FRM)
       pn_transport_logf(transport, "  <- %s", "AMQP");
     return 8;
+  case PNI_PROTOCOL_INSUFFICIENT:
+    if (!eos) return 0;
+    /* Fallthru */
+  default:
+    break;
   }
-  return 0;
+  char quoted[1024];
+  pn_quote_data(quoted, 1024, bytes, available);
+  pn_do_error(transport, "amqp:connection:framing-error",
+              "%s header mismatch: %s ['%s']%s", "AMQP", pni_protocol_name(protocol), quoted,
+              !eos ? "" : " (connection aborted)");
+  return PN_EOS;
 }
 
 static ssize_t pn_input_read_amqp(pn_transport_t* transport, unsigned int layer, const char* bytes, size_t available)
@@ -1842,6 +2000,8 @@ int pn_process(pn_transport_t *transport)
   return 0;
 }
 
+#define AMQP_HEADER ("AMQP\x00\x01\x00\x00")
+
 static ssize_t pn_output_write_amqp_header(pn_transport_t* transport, unsigned int layer, char* bytes, size_t available)
 {
   if (transport->disp->trace & PN_TRACE_FRM)
@@ -2061,7 +2221,7 @@ pn_timestamp_t pn_transport_tick(pn_transport_t *transport, pn_timestamp_t now)
 {
   pn_timestamp_t r = 0;
   for (int i = 0; i<PN_IO_LAYER_CT; ++i) {
-    if (transport->io_layers[i]->process_tick)
+    if (transport->io_layers[i] && transport->io_layers[i]->process_tick)
       r = pn_timestamp_min(r, transport->io_layers[i]->process_tick(transport, i, now));
   }
   return r;
@@ -2081,24 +2241,6 @@ uint64_t pn_transport_get_frames_input(const pn_transport_t *transport)
   return 0;
 }
 
-/** Pass through input handler */
-ssize_t pn_io_layer_input_passthru(pn_transport_t *transport, unsigned int layer, const char *data, size_t available)
-{
-  if (layer+1<PN_IO_LAYER_CT)
-    return transport->io_layers[layer+1]->process_input(transport, layer+1, data, available);
-  return PN_EOS;
-}
-
-/** Pass through output handler */
-ssize_t pn_io_layer_output_passthru(pn_transport_t *transport, unsigned int layer, char *data, size_t available)
-{
-  if (layer+1<PN_IO_LAYER_CT)
-      return transport->io_layers[layer+1]->process_output(transport, layer+1, data, available);
-  return PN_EOS;
-}
-
-///
-
 // input
 ssize_t pn_transport_capacity(pn_transport_t *transport)  /* <0 == done */
 {
@@ -2252,7 +2394,8 @@ bool pn_transport_quiesced(pn_transport_t *transport)
   else if (pending > 0) return false;
   // no pending at transport, but check if data is buffered in I/O layers
   for (int layer = 0; layer<PN_IO_LAYER_CT; ++layer) {
-    if (transport->io_layers[layer]->buffered_output &&
+    if (transport->io_layers[layer] &&
+        transport->io_layers[layer]->buffered_output &&
         transport->io_layers[layer]->buffered_output( transport ))
       return false;
   }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/1b2be03c/proton-c/src/windows/schannel.c
----------------------------------------------------------------------
diff --git a/proton-c/src/windows/schannel.c b/proton-c/src/windows/schannel.c
index 231349c..e7ef5fd 100644
--- a/proton-c/src/windows/schannel.c
+++ b/proton-c/src/windows/schannel.c
@@ -148,11 +148,8 @@ struct pn_ssl_session_t {
 
 static ssize_t process_input_ssl( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
 static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
-static ssize_t process_input_unknown( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
-static ssize_t process_output_unknown( pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
 static ssize_t process_input_done(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
 static ssize_t process_output_done(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
-static connection_mode_t check_for_ssl_connection( const char *data, size_t len );
 static pn_ssl_session_t *ssn_cache_find( pn_ssl_domain_t *, const char * );
 static void ssl_session_free( pn_ssl_session_t *);
 static size_t buffered_output( pn_transport_t *transport );
@@ -360,13 +357,6 @@ int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain,
   return 0;
 }
 
-const pn_io_layer_t unknown_layer = {
-    process_input_unknown,
-    process_output_unknown,
-    NULL,
-    NULL
-};
-
 const pn_io_layer_t ssl_layer = {
     process_input_ssl,
     process_output_ssl,
@@ -404,12 +394,6 @@ int pn_ssl_init(pn_ssl_t *ssl0, pn_ssl_domain_t *domain, const char *session_id)
 
   ssl->domain = domain;
   domain->ref_count++;
-  if (domain->allow_unsecured) {
-    transport->io_layers[PN_IO_SSL] = &unknown_layer;
-  }
-  else {
-    transport->io_layers[PN_IO_SSL] = &ssl_layer;
-  }
   if (session_id && domain->mode == PN_SSL_MODE_CLIENT)
     ssl->session_id = pn_strdup(session_id);
 
@@ -439,6 +423,11 @@ int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain)
 }
 
 
+bool pn_ssl_allow_unsecured(pn_transport_t *transport)
+{
+  return transport && transport->ssl && transport->ssl->domain && transport->ssl->domain->allow_unsecured;
+}
+
 bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size )
 {
   *buffer = '\0';
@@ -893,12 +882,6 @@ static void start_ssl_shutdown(pn_transport_t *transport)
   ssl_handshake(transport);
 }
 
-static int setup_ssl_connection(pn_transport_t *transport, unsigned int layer)
-{
-  transport->io_layers[layer] = &ssl_layer;
-  return 0;
-}
-
 static void rewind_sc_inbuf(pni_ssl_t *ssl)
 {
   // Decrypted bytes have been drained or double buffered.  Prepare for the next SSL Record.
@@ -1270,92 +1253,6 @@ static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer
 }
 
 
-static int setup_cleartext_connection(pn_transport_t *transport, unsigned int layer)
-{
-  transport->io_layers[layer] = &pni_passthru_layer;
-  return 0;
-}
-
-
-// until we determine if the client is using SSL or not:
-
-static ssize_t process_input_unknown(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len)
-{
-  pn_ssl_t *ssl = transport->ssl;
-  switch (check_for_ssl_connection( input_data, len )) {
-  case SSL_CONNECTION:
-	ssl_log(ssl, "SSL connection detected.\n");
-    setup_ssl_connection(transport, layer);
-	break;
-  case CLEAR_CONNECTION:
-	ssl_log(ssl, "Cleartext connection detected.\n");
-    setup_cleartext_connection(transport, layer);
-	break;
-  default:
-    return 0;
-  }
-  return transport->io_layers[layer]->process_input(transport, layer, input_data, len);
-}
-
-static ssize_t process_output_unknown(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len)
-{
-  // do not do output until we know if SSL is used or not
-  return 0;
-}
-
-static connection_mode_t check_for_ssl_connection( const char *data, size_t len )
-{
-  if (len >= 5) {
-    const unsigned char *buf = (unsigned char *)data;
-    /*
-     * SSLv2 Client Hello format
-     * http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html
-     *
-     * Bytes 0-1: RECORD-LENGTH
-     * Byte    2: MSG-CLIENT-HELLO (1)
-     * Byte    3: CLIENT-VERSION-MSB
-     * Byte    4: CLIENT-VERSION-LSB
-     *
-     * Allowed versions:
-     * 2.0 - SSLv2
-     * 3.0 - SSLv3
-     * 3.1 - TLS 1.0
-     * 3.2 - TLS 1.1
-     * 3.3 - TLS 1.2
-     *
-     * The version sent in the Client-Hello is the latest version supported by
-     * the client. NSS may send version 3.x in an SSLv2 header for
-     * maximum compatibility.
-     */
-    int isSSL2Handshake = buf[2] == 1 &&   // MSG-CLIENT-HELLO
-      ((buf[3] == 3 && buf[4] <= 3) ||    // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
-       (buf[3] == 2 && buf[4] == 0));     // SSL 2
-
-    /*
-     * SSLv3/TLS Client Hello format
-     * RFC 2246
-     *
-     * Byte    0: ContentType (handshake - 22)
-     * Bytes 1-2: ProtocolVersion {major, minor}
-     *
-     * Allowed versions:
-     * 3.0 - SSLv3
-     * 3.1 - TLS 1.0
-     * 3.2 - TLS 1.1
-     * 3.3 - TLS 1.2
-     */
-    int isSSL3Handshake = buf[0] == 22 &&  // handshake
-      (buf[1] == 3 && buf[2] <= 3);       // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
-
-    if (isSSL2Handshake || isSSL3Handshake) {
-      return SSL_CONNECTION;
-    } else {
-      return CLEAR_CONNECTION;
-    }
-  }
-  return UNKNOWN_CONNECTION;
-}
-
 static ssize_t process_input_done(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len)
 {
   return PN_EOS;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org