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/master Commit: 1b2be03c748ef5a57cf181f8373b9b6e8f8cfd22 Parents: 9c9872b Author: Andrew Stitcher <astitc...@apache.org> Authored: Wed Aug 20 23:39:34 2014 -0400 Committer: Andrew Stitcher <astitc...@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-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org