From ba936a869d51d24cfdbfb6d89670fd8aa0c7123a Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Tue, 21 Sep 2021 11:07:54 +0100 Subject: [PATCH] Add `accept_protocol_versions` option. --- ChangeLog.txt | 4 +- man/mosquitto.conf.5.xml | 23 ++++++++ mosquitto.conf | 15 ++++++ src/conf.c | 26 ++++++++- src/handle_connect.c | 24 +++++++++ src/mosquitto_broker_internal.h | 3 ++ test/broker/01-connect-accept-protocol.py | 66 +++++++++++++++++++++++ test/broker/Makefile | 1 + test/broker/test.py | 1 + 9 files changed, 161 insertions(+), 2 deletions(-) create mode 100755 test/broker/01-connect-accept-protocol.py diff --git a/ChangeLog.txt b/ChangeLog.txt index bb50d104..49c785cd 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -58,8 +58,10 @@ Broker: - Add `mosquitto_client_port()` function for plugins. - Add `global_max_clients` option to allow limiting client sessions globally on the broker. -- Add `global_max_connections` option to allow limiting client onnections globally +- Add `global_max_connections` option to allow limiting client connections globally on the broker. +- Add `accept_protocol_versions` option to allow limiting which MQTT protocol + versions are allowed for a particular listener. Client library: - Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index 97d7ef57..c151fc8a 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -1138,6 +1138,29 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S General Options + + versions + + + Accepted protocol versions. This sets what versions + of the MQTT protocol will be accepted on this + listener. Can be any combination of 3, 4, 5 in a + comma separated list, e.g. + + +# Allow v5.0 only: +listener 1883 +accept_protocol_versions 5 + +# Allow v3.1 and v3.1.1: +listener 1884 +accept_protocol_versions 3, 4 + + Defaults to allowing all versions. + + Reloaded on reload signal. + + address diff --git a/mosquitto.conf b/mosquitto.conf index 76037c3b..9970c7a6 100644 --- a/mosquitto.conf +++ b/mosquitto.conf @@ -296,6 +296,21 @@ # cafile, certfile, keyfile, ciphers, and ciphers_tls13 options are supported. #protocol mqtt +# Accepted protocol versions. This sets what versions of the MQTT protocol will +# be accepted on this listener. Can be any combination of 3, 4, 5 in a comma +# separated list, e.g. +# +# # Allow v5.0 only: +# listener 1883 +# accept_protocol_versions 5 +# +# # Allow v3.1 and v3.1.1: +# listener 1884 +# accept_protocol_versions 3, 4 +# +# Defaults to allowing all versions. +#accept_protocol_versions 3,4,5 + # Set use_username_as_clientid to true to replace the clientid that a client # connected with with its username. This allows authentication to be tied to # the clientid, which means that it is possible to prevent one client diff --git a/src/conf.c b/src/conf.c index 3d68d13e..8278abd6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -826,7 +826,31 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload, } token = strtok_r((*buf), " ", &saveptr); if(token){ - if(!strcmp(token, "acl_file")){ + if(!strcmp(token, "accept_protocol_version")){ + if(cur_listener == &config->default_listener){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: You must define a listener before using he %s option.", "accept_protocol_version"); + return MOSQ_ERR_INVAL; + } + cur_listener->disable_protocol_v3 = true; + cur_listener->disable_protocol_v4 = true; + cur_listener->disable_protocol_v5 = true; + if(saveptr == NULL){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty %s value in configuration.", "accept_protocol_version"); + return MOSQ_ERR_INVAL; + } + token = strtok_r(saveptr, ", \t", &saveptr); + while(token){ + if(!strcmp(token, "3")){ + cur_listener->disable_protocol_v3 = false; + }else if(!strcmp(token, "4")){ + cur_listener->disable_protocol_v4 = false; + }else if(!strcmp(token, "5")){ + cur_listener->disable_protocol_v5 = false; + } + + token = strtok_r(NULL, ", \t", &saveptr); + } + }else if(!strcmp(token, "acl_file")){ conf__set_cur_security_options(config, cur_listener, &cur_security_options); if(reload){ mosquitto__free(cur_security_options->acl_file); diff --git a/src/handle_connect.c b/src/handle_connect.c index 901dd990..32c3e90a 100644 --- a/src/handle_connect.c +++ b/src/handle_connect.c @@ -434,6 +434,19 @@ error_cleanup: } +static int check_protocol_version(struct mosquitto__listener *listener, int protocol_version) +{ + if((protocol_version == 3 && listener->disable_protocol_v3 == false) + || (protocol_version == 4 && listener->disable_protocol_v4 == false) + || (protocol_version == 5 && listener->disable_protocol_v5 == false) + ){ + + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_NOT_SUPPORTED; + } +} + int handle__connect(struct mosquitto *context) { @@ -503,6 +516,17 @@ int handle__connect(struct mosquitto *context) rc = MOSQ_ERR_PROTOCOL; goto handle_connect_error; } + if(check_protocol_version(context->listener, protocol_version)){ + if(protocol_version == 3 || protocol_version == 4){ + context->protocol = mosq_p_mqtt311; + send__connack(context, 0, CONNACK_REFUSED_PROTOCOL_VERSION, NULL); + }else{ + context->protocol = mosq_p_mqtt5; + send__connack(context, 0, MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION, NULL); + } + rc = MOSQ_ERR_NOT_SUPPORTED; + goto handle_connect_error; + } if(!strcmp(protocol_name, PROTOCOL_NAME_v31)){ if((protocol_version&0x7F) != PROTOCOL_VERSION_v31){ if(db.config->connection_messages == true){ diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 0d76c2ce..5d63d7b2 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -243,6 +243,9 @@ struct mosquitto__listener { #ifdef WITH_UNIX_SOCKETS char *unix_socket_path; #endif + bool disable_protocol_v3; + bool disable_protocol_v4; + bool disable_protocol_v5; }; diff --git a/test/broker/01-connect-accept-protocol.py b/test/broker/01-connect-accept-protocol.py new file mode 100755 index 00000000..4f4bf00f --- /dev/null +++ b/test/broker/01-connect-accept-protocol.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# Test accept_protocol_version option + +from mosq_test_helper import * + +def write_config(filename, port, accept): + with open(filename, 'w') as f: + f.write("listener %s\n" % (port)) + f.write("allow_anonymous true\n") + f.write("accept_protocol_version %s\n" % (accept)) + +def do_test(accept, expect_success): + port = mosq_test.get_port() + + conf_file = os.path.basename(__file__).replace('.py', '.conf') + write_config(conf_file, port, accept) + + broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port) + + try: + for proto_ver in [3, 4, 5]: + rc = 1 + connect_packet = mosq_test.gen_connect("accept-protocol-test-%d" % (proto_ver), proto_ver=proto_ver) + + if proto_ver == 5: + if proto_ver in expect_success: + connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver) + else: + connack_packet = mosq_test.gen_connack(rc=mqtt5_rc.MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION, proto_ver=proto_ver, properties=None) + else: + if proto_ver in expect_success: + connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver) + else: + connack_packet = mosq_test.gen_connack(rc=1, proto_ver=proto_ver) + + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + sock.close() + rc = 0 + except mosq_test.TestError: + pass + finally: + if write_config is not None: + os.remove(conf_file) + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + print("proto_ver=%d" % (proto_ver)) + exit(rc) + + +do_test(accept="3,4,5", expect_success=[3, 4, 5]) +do_test(accept="5,4,3", expect_success=[3, 4, 5]) +do_test(accept="3 ,4, 5", expect_success=[3, 4, 5]) +do_test(accept=" , 3 , 4 , 5 ", expect_success=[3, 4, 5]) +do_test(accept="3", expect_success=[3]) +do_test(accept="4", expect_success=[4]) +do_test(accept="5", expect_success=[5]) +do_test(accept="3,4", expect_success=[3, 4]) +do_test(accept="3,5", expect_success=[3, 5]) +do_test(accept="4,3", expect_success=[3, 4]) +do_test(accept="4,5", expect_success=[4, 5]) +do_test(accept="5,3", expect_success=[3, 5]) diff --git a/test/broker/Makefile b/test/broker/Makefile index bd80ce4c..54141575 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -24,6 +24,7 @@ msg_sequence_test: 01 : ./01-connect-575314.py + ./01-connect-accept-protocol.py ./01-connect-allow-anonymous.py ./01-connect-disconnect-v5.py ./01-connect-global-max-clients.py diff --git a/test/broker/test.py b/test/broker/test.py index 273fedea..ebc206ec 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -6,6 +6,7 @@ import ptest tests = [ #(ports required, 'path'), (1, './01-connect-575314.py'), + (1, './01-connect-accept-protocol.py'), (1, './01-connect-allow-anonymous.py'), (1, './01-connect-disconnect-v5.py'), (1, './01-connect-global-max-clients.py'),