From 8ad81a99173960a745ed5710b4ece48c0fa7abd7 Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Mon, 12 Jan 2026 23:51:02 +0000 Subject: [PATCH] MOSQ_EVT_ACL_CHECK event is now passed message properties where possible. Closes #3176. --- ChangeLog.txt | 1 + src/context.c | 1 + src/database.c | 3 +- src/handle_publish.c | 5 +- src/handle_subscribe.c | 2 +- src/handle_unsubscribe.c | 2 +- src/http_api.c | 2 +- src/mosquitto_broker_internal.h | 2 +- src/plugin_acl_check.c | 10 ++-- src/plugin_public.c | 1 + src/retain.c | 4 +- src/subs.c | 2 +- test/broker/09-plugin-auth-acl-pub-prop.py | 54 ++++++++++++++++++++++ test/broker/Makefile | 1 + test/broker/c/auth_plugin_v5.c | 4 ++ test/broker/test.py | 1 + test/unit/broker/persist_read_stubs.c | 3 +- test/unit/broker/persist_write_stubs.c | 3 +- test/unit/broker/subs_stubs.c | 3 +- 19 files changed, 87 insertions(+), 17 deletions(-) create mode 100755 test/broker/09-plugin-auth-acl-pub-prop.py diff --git a/ChangeLog.txt b/ChangeLog.txt index 5c42b178..e1a261ff 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -149,6 +149,7 @@ - Plugins can now use the `next_s` and `next_ms` members of the tick event data struct to set a minimum interval that the broker will wait before calling the tick callback again. +- MOSQ_EVT_ACL_CHECK event is now passed message properties where possible. # Plugins - Add acl-file plugin. diff --git a/src/context.c b/src/context.c index a7f85c49..ca4784d9 100644 --- a/src/context.c +++ b/src/context.c @@ -227,6 +227,7 @@ void context__send_will(struct mosquitto *ctxt) ctxt->will->msg.payload, (uint8_t)ctxt->will->msg.qos, ctxt->will->msg.retain, + ctxt->will->properties, MOSQ_ACL_WRITE) == MOSQ_ERR_SUCCESS){ /* Unexpected disconnect, queue the client will. */ diff --git a/src/database.c b/src/database.c index 47e3a7cd..1ba76d1d 100644 --- a/src/database.c +++ b/src/database.c @@ -1321,7 +1321,8 @@ static void db__client_messages_check_acl(struct mosquitto *context, struct mosq } if(mosquitto_acl_check(context, base_msg->data.topic, base_msg->data.payloadlen, base_msg->data.payload, - base_msg->data.qos, base_msg->data.retain, access) != MOSQ_ERR_SUCCESS){ + base_msg->data.qos, base_msg->data.retain, + base_msg->data.properties, access) != MOSQ_ERR_SUCCESS){ DL_DELETE((*head), client_msg); decrement_stats_fn(msg_data, client_msg); diff --git a/src/handle_publish.c b/src/handle_publish.c index 2a3ac0d0..2485281b 100644 --- a/src/handle_publish.c +++ b/src/handle_publish.c @@ -357,7 +357,10 @@ int handle__publish(struct mosquitto *context) } /* Check for topic access */ - rc = mosquitto_acl_check(context, base_msg->data.topic, base_msg->data.payloadlen, base_msg->data.payload, base_msg->data.qos, base_msg->data.retain, MOSQ_ACL_WRITE); + rc = mosquitto_acl_check(context, + base_msg->data.topic, base_msg->data.payloadlen, base_msg->data.payload, + base_msg->data.qos, base_msg->data.retain, base_msg->data.properties, + MOSQ_ACL_WRITE); if(rc == MOSQ_ERR_ACL_DENIED){ log__printf(NULL, MOSQ_LOG_DEBUG, "Denied PUBLISH from %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", diff --git a/src/handle_subscribe.c b/src/handle_subscribe.c index 61a532fc..b30a2ed6 100644 --- a/src/handle_subscribe.c +++ b/src/handle_subscribe.c @@ -187,7 +187,7 @@ int handle__subscribe(struct mosquitto *context) } allowed = true; - rc2 = mosquitto_acl_check(context, sub.topic_filter, 0, NULL, qos, false, MOSQ_ACL_SUBSCRIBE); + rc2 = mosquitto_acl_check(context, sub.topic_filter, 0, NULL, qos, false, properties, MOSQ_ACL_SUBSCRIBE); switch(rc2){ case MOSQ_ERR_SUCCESS: break; diff --git a/src/handle_unsubscribe.c b/src/handle_unsubscribe.c index 4f53d043..b5047ad4 100644 --- a/src/handle_unsubscribe.c +++ b/src/handle_unsubscribe.c @@ -120,7 +120,7 @@ int handle__unsubscribe(struct mosquitto *context) /* ACL check */ allowed = true; - rc = mosquitto_acl_check(context, sub.topic_filter, 0, NULL, 0, false, MOSQ_ACL_UNSUBSCRIBE); + rc = mosquitto_acl_check(context, sub.topic_filter, 0, NULL, 0, false, properties, MOSQ_ACL_UNSUBSCRIBE); switch(rc){ case MOSQ_ERR_SUCCESS: break; diff --git a/src/http_api.c b/src/http_api.c index c468143f..9c235804 100644 --- a/src/http_api.c +++ b/src/http_api.c @@ -357,7 +357,7 @@ static int check_access(struct mosquitto__listener *listener, struct MHD_Connect /* Authentication */ auth_rc = mosquitto_basic_auth(&context); if(auth_rc == MOSQ_ERR_SUCCESS){ - acl_rc = mosquitto_acl_check(&context, url, 0, NULL, 0, false, MOSQ_ACL_READ); + acl_rc = mosquitto_acl_check(&context, url, 0, NULL, 0, false, NULL, MOSQ_ACL_READ); } MHD_free(context.username); MHD_free(context.password); diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 74d06be6..0051b22a 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -911,7 +911,7 @@ int config__plugin_add_secopt(mosquitto_plugin_id_t *plugin, struct mosquitto__s int mosquitto_security_init(bool reload); int mosquitto_security_cleanup(bool reload); -int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access); +int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access); int mosquitto_basic_auth(struct mosquitto *context); int mosquitto_psk_key_get(struct mosquitto *context, const char *hint, const char *identity, char *key, int max_key_len); diff --git a/src/plugin_acl_check.c b/src/plugin_acl_check.c index 16f4e7ca..7ce6b454 100644 --- a/src/plugin_acl_check.c +++ b/src/plugin_acl_check.c @@ -102,7 +102,7 @@ static int acl__check_dollar(const char *topic, int access) } -static int plugin__acl_check(struct mosquitto__security_options *opts, struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access) +static int plugin__acl_check(struct mosquitto__security_options *opts, struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access) { int rc = MOSQ_ERR_PLUGIN_DEFER; struct mosquitto_acl_msg msg; @@ -127,7 +127,7 @@ static int plugin__acl_check(struct mosquitto__security_options *opts, struct mo event_data.payload = payload; event_data.qos = qos; event_data.retain = retain; - event_data.properties = NULL; + event_data.properties = properties; rc = cb_base->cb(MOSQ_EVT_ACL_CHECK, &event_data, cb_base->userdata); if(rc != MOSQ_ERR_PLUGIN_DEFER && rc != MOSQ_ERR_PLUGIN_IGNORE){ return rc; @@ -138,7 +138,7 @@ static int plugin__acl_check(struct mosquitto__security_options *opts, struct mo } -int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access) +int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access) { int rc; int rc_final; @@ -164,7 +164,7 @@ int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t p * If per listener_settings is false, these are global and listener plugins. */ if(db.config->security_options.plugin_callbacks.acl_check){ rc = plugin__acl_check(&db.config->security_options, context, topic, payloadlen, - payload, qos, retain, access); + payload, qos, retain, properties, access); if(rc == MOSQ_ERR_PLUGIN_IGNORE){ /* Do nothing, this is as if the plugin doesn't exist */ @@ -179,7 +179,7 @@ int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t p if(context->listener){ if(context->listener->security_options->plugin_callbacks.acl_check){ rc = plugin__acl_check(context->listener->security_options, context, topic, payloadlen, - payload, qos, retain, access); + payload, qos, retain, properties, access); if(rc == MOSQ_ERR_PLUGIN_IGNORE){ /* Do nothing, this is as if the plugin doesn't exist */ diff --git a/src/plugin_public.c b/src/plugin_public.c index 13c13d60..769ad4c0 100644 --- a/src/plugin_public.c +++ b/src/plugin_public.c @@ -402,6 +402,7 @@ static void check_subscription_acls(struct mosquitto *context) NULL, 0, /* FIXME */ false, + NULL, MOSQ_ACL_SUBSCRIBE); if(rc != MOSQ_ERR_SUCCESS){ diff --git a/src/retain.c b/src/retain.c index 3694e14a..e6b64c00 100644 --- a/src/retain.c +++ b/src/retain.c @@ -237,7 +237,7 @@ static int retain__process(struct mosquitto__retainhier *branch, struct mosquitt retained = branch->retained; rc = mosquitto_acl_check(context, retained->data.topic, retained->data.payloadlen, retained->data.payload, - retained->data.qos, retained->data.retain, MOSQ_ACL_READ); + retained->data.qos, retained->data.retain, retained->data.properties, MOSQ_ACL_READ); if(rc == MOSQ_ERR_ACL_DENIED){ return MOSQ_ERR_SUCCESS; }else if(rc != MOSQ_ERR_SUCCESS){ @@ -254,7 +254,7 @@ static int retain__process(struct mosquitto__retainhier *branch, struct mosquitt retain_ctxt.listener = retained->source_listener; rc = mosquitto_acl_check(&retain_ctxt, retained->data.topic, retained->data.payloadlen, retained->data.payload, - retained->data.qos, retained->data.retain, MOSQ_ACL_WRITE); + retained->data.qos, retained->data.retain, retained->data.properties, MOSQ_ACL_WRITE); if(rc == MOSQ_ERR_ACL_DENIED){ return MOSQ_ERR_SUCCESS; }else if(rc != MOSQ_ERR_SUCCESS){ diff --git a/src/subs.c b/src/subs.c index a87f8221..535def88 100644 --- a/src/subs.c +++ b/src/subs.c @@ -73,7 +73,7 @@ static int subs__send(struct mosquitto__subleaf *leaf, const char *topic, uint8_ int rc2; /* Check for ACL topic access. */ - rc2 = mosquitto_acl_check(leaf->context, topic, stored->data.payloadlen, stored->data.payload, stored->data.qos, stored->data.retain, MOSQ_ACL_READ); + rc2 = mosquitto_acl_check(leaf->context, topic, stored->data.payloadlen, stored->data.payload, stored->data.qos, stored->data.retain, stored->data.properties, MOSQ_ACL_READ); if(rc2 == MOSQ_ERR_ACL_DENIED){ return MOSQ_ERR_SUCCESS; }else if(rc2 == MOSQ_ERR_SUCCESS){ diff --git a/test/broker/09-plugin-auth-acl-pub-prop.py b/test/broker/09-plugin-auth-acl-pub-prop.py new file mode 100755 index 00000000..b35f8bc8 --- /dev/null +++ b/test/broker/09-plugin-auth-acl-pub-prop.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +# Bug specific test - if a QoS2 publish is denied, then we publish again with +# the same mid to a topic that is allowed, does it work properly? + +from mosq_test_helper import * + +def write_config(filename, port): + with open(filename, 'w') as f: + f.write("listener %d\n" % (port)) + f.write("plugin c/auth_plugin_v5.so\n") + f.write("allow_anonymous false\n") + +def do_test(): + port = mosq_test.get_port() + conf_file = os.path.basename(__file__).replace('.py', '.conf') + write_config(conf_file, port) + + rc = 1 + connect_packet = mosq_test.gen_connect("connect-uname-pwd-test", username="test-username", password="cnwTICONIURW", proto_ver=5) + connack_packet = mosq_test.gen_connack(rc=0, proto_ver=5) + + mid = 1 + props = mqtt5_props.gen_string_pair_prop(mqtt5_props.USER_PROPERTY, "custom-name", "custom-value") + publish_allowed_packet = mosq_test.gen_publish("bad-topic", qos=1, mid=mid, payload="message", properties=props, proto_ver=5) + puback_allowed_packet = mosq_test.gen_puback(mid, reason_code=mqtt5_rc.NO_MATCHING_SUBSCRIBERS, proto_ver=5) + + mid = 2 + publish_denied_packet = mosq_test.gen_publish("bad-topic", qos=1, mid=mid, payload="message", proto_ver=5) + puback_denied_packet = mosq_test.gen_puback(mid, reason_code=mqtt5_rc.NOT_AUTHORIZED, proto_ver=5) + + broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port) + + try: + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + + mosq_test.do_send_receive(sock, publish_allowed_packet, puback_allowed_packet, "puback allowed") + mosq_test.do_send_receive(sock, publish_denied_packet, puback_denied_packet, "puback denied") + sock.close() + rc = 0 + except mosq_test.TestError: + pass + finally: + os.remove(conf_file) + broker.terminate() + if mosq_test.wait_for_subprocess(broker): + print("broker not terminated") + if rc == 0: rc=1 + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + exit(rc) + +do_test() diff --git a/test/broker/Makefile b/test/broker/Makefile index ae4dd5f4..ef62f26b 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -197,6 +197,7 @@ endif ./09-plugin-acl-access-variants.py ./09-plugin-acl-change.py ./09-plugin-auth-acl-pub.py + ./09-plugin-auth-acl-pub-prop.py ./09-plugin-auth-acl-sub-denied.py ./09-plugin-auth-acl-sub.py ./09-plugin-auth-context-params.py diff --git a/test/broker/c/auth_plugin_v5.c b/test/broker/c/auth_plugin_v5.c index 914ed822..63e045c3 100644 --- a/test/broker/c/auth_plugin_v5.c +++ b/test/broker/c/auth_plugin_v5.c @@ -45,6 +45,7 @@ int mosquitto_auth_acl_check_v5(int event, void *event_data, void *user_data) { struct mosquitto_evt_acl_check *ed = event_data; const char *username = mosquitto_client_username(ed->client); + char *prop_name, *prop_value; (void)user_data; @@ -64,7 +65,10 @@ int mosquitto_auth_acl_check_v5(int event, void *event_data, void *user_data) }else{ return MOSQ_ERR_ACL_DENIED; } + }else if(mosquitto_property_read_string_pair(ed->properties, MQTT_PROP_USER_PROPERTY, &prop_name, &prop_value, false) + && !strcmp(prop_name, "custom-name") && !strcmp(prop_value, "custom-value")){ + return MOSQ_ERR_SUCCESS; }else{ return MOSQ_ERR_ACL_DENIED; } diff --git a/test/broker/test.py b/test/broker/test.py index 3925c6b9..7ec31500 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -168,6 +168,7 @@ tests = [ (1, './09-plugin-acl-access-variants.py'), (1, './09-plugin-acl-change.py'), (1, './09-plugin-auth-acl-pub.py'), + (1, './09-plugin-auth-acl-pub-prop.py'), (1, './09-plugin-auth-acl-sub-denied.py'), (1, './09-plugin-auth-acl-sub.py'), (1, './09-plugin-auth-context-params.py'), diff --git a/test/unit/broker/persist_read_stubs.c b/test/unit/broker/persist_read_stubs.c index db65929b..c6b6b4d4 100644 --- a/test/unit/broker/persist_read_stubs.c +++ b/test/unit/broker/persist_read_stubs.c @@ -70,7 +70,7 @@ int send__pingreq(struct mosquitto *mosq) } -int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access) +int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access) { UNUSED(context); UNUSED(topic); @@ -78,6 +78,7 @@ int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t p UNUSED(payload); UNUSED(qos); UNUSED(retain); + UNUSED(properties); UNUSED(access); return MOSQ_ERR_SUCCESS; diff --git a/test/unit/broker/persist_write_stubs.c b/test/unit/broker/persist_write_stubs.c index 7cf3b605..325ed755 100644 --- a/test/unit/broker/persist_write_stubs.c +++ b/test/unit/broker/persist_write_stubs.c @@ -58,7 +58,7 @@ int send__pingreq(struct mosquitto *mosq) } -int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access) +int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access) { UNUSED(context); UNUSED(topic); @@ -66,6 +66,7 @@ int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t p UNUSED(payload); UNUSED(qos); UNUSED(retain); + UNUSED(properties); UNUSED(access); return MOSQ_ERR_SUCCESS; diff --git a/test/unit/broker/subs_stubs.c b/test/unit/broker/subs_stubs.c index 6c0a4c28..c100406b 100644 --- a/test/unit/broker/subs_stubs.c +++ b/test/unit/broker/subs_stubs.c @@ -76,7 +76,7 @@ int send__pubrel(struct mosquitto *mosq, uint16_t mid, const mosquitto_property } -int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, int access) +int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t payloadlen, void *payload, uint8_t qos, bool retain, mosquitto_property *properties, int access) { UNUSED(context); UNUSED(topic); @@ -84,6 +84,7 @@ int mosquitto_acl_check(struct mosquitto *context, const char *topic, uint32_t p UNUSED(payload); UNUSED(qos); UNUSED(retain); + UNUSED(properties); UNUSED(access); return MOSQ_ERR_SUCCESS;