diff --git a/include/zmq.h b/include/zmq.h index 2cde8230a1..87b2f8e600 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -570,6 +570,7 @@ ZMQ_EXPORT void zmq_threadclose (void* thread); #define ZMQ_GSSAPI_PRINCIPAL_NAMETYPE 90 #define ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE 91 #define ZMQ_BINDTODEVICE 92 +#define ZMQ_ZAP_DOMAIN_REQUIRED 93 /* DRAFT 0MQ socket events and monitoring */ /* Unspecified system errors during handshake. Event value is an errno. */ diff --git a/src/curve_server.cpp b/src/curve_server.cpp index 23173045d0..b14f21584c 100644 --- a/src/curve_server.cpp +++ b/src/curve_server.cpp @@ -390,7 +390,9 @@ int zmq::curve_server_t::process_initiate (msg_t *msg_) rc = crypto_box_beforenm (cn_precom, cn_client, cn_secret); zmq_assert (rc == 0); - if (zap_required ()) { + // Given this is a backward-incompatible change, it's behind a socket + // option disabled by default. + if (zap_required () || !options.zap_domain_required) { // Use ZAP protocol (RFC 27) to authenticate the user. rc = session->zap_connect (); if (rc == 0) { @@ -404,6 +406,10 @@ int zmq::curve_server_t::process_initiate (msg_t *msg_) rc = receive_and_process_zap_reply (); if (rc == -1) return -1; + } else if (!options.zap_domain_required) { + // This supports the Stonehouse pattern (encryption without + // authentication) in legacy mode (allow empty ZAP domain). + state = sending_ready; } else { session->get_socket ()->event_handshake_failed_no_detail ( session->get_endpoint (), EFAULT); diff --git a/src/options.cpp b/src/options.cpp index 5382140406..bf46586b03 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -89,7 +89,8 @@ zmq::options_t::options_t () : heartbeat_ttl (0), heartbeat_interval (0), heartbeat_timeout (-1), - use_fd (-1) + use_fd (-1), + zap_domain_required (false) { memset (curve_public_key, 0, CURVE_KEYSIZE); memset (curve_secret_key, 0, CURVE_KEYSIZE); @@ -628,6 +629,14 @@ int zmq::options_t::setsockopt (int option_, const void *optval_, } break; + case ZMQ_ZAP_DOMAIN_REQUIRED: + if (is_int) { + zap_domain_required = (value != 0); + return 0; + } + break; + + default: #if defined (ZMQ_ACT_MILITANT) // There are valid scenarios for probing with unknown socket option @@ -1052,6 +1061,13 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_) } break; + case ZMQ_ZAP_DOMAIN_REQUIRED: + if (is_int) { + *value = zap_domain_required; + return 0; + } + break; + default: #if defined (ZMQ_ACT_MILITANT) malformed = false; diff --git a/src/options.hpp b/src/options.hpp index 2597266004..7861f938e5 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -242,6 +242,9 @@ namespace zmq // Device to bind the underlying socket to, eg. VRF or interface std::string bound_device; + + // Enforce a non-empty ZAP domain requirement for PLAIN auth + bool zap_domain_required; }; } diff --git a/src/plain_server.cpp b/src/plain_server.cpp index 5fabb1ca45..6e9fad53e6 100644 --- a/src/plain_server.cpp +++ b/src/plain_server.cpp @@ -47,7 +47,10 @@ zmq::plain_server_t::plain_server_t (session_base_t *session_, // Note that there is no point to PLAIN if ZAP is not set up to handle the // username and password, so if ZAP is not configured it is considered a // failure. - zmq_assert (zap_required()); + // Given this is a backward-incompatible change, it's behind a socket + // option disabled by default. + if (options.zap_domain_required) + zmq_assert (zap_required()); } zmq::plain_server_t::~plain_server_t () diff --git a/src/zmq_draft.h b/src/zmq_draft.h index cf2b452b0b..7df90e4515 100644 --- a/src/zmq_draft.h +++ b/src/zmq_draft.h @@ -50,6 +50,7 @@ #define ZMQ_GSSAPI_PRINCIPAL_NAMETYPE 90 #define ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE 91 #define ZMQ_BINDTODEVICE 92 +#define ZMQ_ZAP_DOMAIN_REQUIRED 93 /* DRAFT 0MQ socket events and monitoring */ /* Unspecified system errors during handshake. Event value is an errno. */ diff --git a/tests/test_security_zap.cpp b/tests/test_security_zap.cpp index 383dbd9718..a4503f3c87 100644 --- a/tests/test_security_zap.cpp +++ b/tests/test_security_zap.cpp @@ -324,6 +324,7 @@ void test_zap_errors (socket_config_fn server_socket_config_, shutdown_context_and_server_side (ctx, zap_thread, server, server_mon, handler); +#ifdef ZMQ_ZAP_DOMAIN_REQUIRED // no ZAP handler fprintf (stderr, "test_zap_unsuccessful no ZAP handler started\n"); setup_context_and_server_side (&ctx, &handler, &zap_thread, &server, @@ -339,6 +340,7 @@ void test_zap_errors (socket_config_fn server_socket_config_, client_socket_config_, client_socket_config_data_); shutdown_context_and_server_side (ctx, zap_thread, server, server_mon, handler); +#endif // ZAP handler disconnecting on first message fprintf(stderr, "test_zap_unsuccessful ZAP handler disconnects\n"); diff --git a/tests/testutil_security.hpp b/tests/testutil_security.hpp index c373622505..e106171415 100644 --- a/tests/testutil_security.hpp +++ b/tests/testutil_security.hpp @@ -114,6 +114,13 @@ void socket_config_curve_server (void *server, void *server_secret) rc = zmq_setsockopt (server, ZMQ_ZAP_DOMAIN, test_zap_domain, strlen (test_zap_domain)); assert (rc == 0); + +#ifdef ZMQ_ZAP_DOMAIN_REQUIRED + int required = 1; + rc = zmq_setsockopt (server, ZMQ_ZAP_DOMAIN_REQUIRED, &required, + sizeof (int)); + assert (rc == 0); +#endif } struct curve_client_data_t