From 46442193f330f6090c6a6e2c02ebd31e13397dbc Mon Sep 17 00:00:00 2001 From: Abilio Marques Date: Mon, 14 Mar 2022 07:52:03 +0100 Subject: [PATCH] add example plugin that jails clients Signed-off-by: Abilio Marques --- plugins/examples/CMakeLists.txt | 1 + plugins/examples/Makefile | 1 + plugins/examples/topic-jail/CMakeLists.txt | 26 ++ plugins/examples/topic-jail/Makefile | 29 ++ .../topic-jail/mosquitto_topic_jail.c | 257 ++++++++++++++++++ plugins/examples/topic-jail/test.conf | 1 + plugins/examples/topic-jail/test.sh | 3 + 7 files changed, 318 insertions(+) create mode 100644 plugins/examples/topic-jail/CMakeLists.txt create mode 100644 plugins/examples/topic-jail/Makefile create mode 100644 plugins/examples/topic-jail/mosquitto_topic_jail.c create mode 100644 plugins/examples/topic-jail/test.conf create mode 100755 plugins/examples/topic-jail/test.sh diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt index 10f8a678..40733f62 100644 --- a/plugins/examples/CMakeLists.txt +++ b/plugins/examples/CMakeLists.txt @@ -17,4 +17,5 @@ add_subdirectory(payload-size-stats) add_subdirectory(plugin-event-stats) add_subdirectory(print-ip-on-publish) add_subdirectory(topic-modification) +add_subdirectory(topic-jail) add_subdirectory(wildcard-temp) diff --git a/plugins/examples/Makefile b/plugins/examples/Makefile index dbc1aca0..36baa883 100644 --- a/plugins/examples/Makefile +++ b/plugins/examples/Makefile @@ -16,6 +16,7 @@ DIRS= \ plugin-event-stats \ print-ip-on-publish \ topic-modification \ + topic-jail \ wildcard-temp .PHONY : all binary check clean reallyclean test test-compile install uninstall diff --git a/plugins/examples/topic-jail/CMakeLists.txt b/plugins/examples/topic-jail/CMakeLists.txt new file mode 100644 index 00000000..51b4c9ca --- /dev/null +++ b/plugins/examples/topic-jail/CMakeLists.txt @@ -0,0 +1,26 @@ +set (PLUGIN_NAME mosquitto_topic_jail) + +add_library(${PLUGIN_NAME} MODULE + ${PLUGIN_NAME}.c +) + +target_include_directories(${PLUGIN_NAME} PRIVATE + "${OPENSSL_INCLUDE_DIR}" + "${STDBOOL_H_PATH}" + "${STDINT_H_PATH}" + "${mosquitto_SOURCE_DIR}" + "${mosquitto_SOURCE_DIR}/include" +) + +link_directories(${mosquitto_SOURCE_DIR}) + +set_target_properties(${PLUGIN_NAME} PROPERTIES + PREFIX "" + POSITION_INDEPENDENT_CODE 1 +) +if(WIN32) + target_link_libraries(${PLUGIN_NAME} mosquitto) +endif() + +# Don't install, these are example plugins only. +#install(TARGETS ${PLUGIN_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") diff --git a/plugins/examples/topic-jail/Makefile b/plugins/examples/topic-jail/Makefile new file mode 100644 index 00000000..e4792d6c --- /dev/null +++ b/plugins/examples/topic-jail/Makefile @@ -0,0 +1,29 @@ +R=../../.. +include ${R}/config.mk + +.PHONY : all binary check clean reallyclean test install uninstall + +PLUGIN_NAME=mosquitto_topic_jail +PLUGIN_CFLAGS+=-I${R}/include -I${R}/ + +all : binary + +binary : ${PLUGIN_NAME}.so + +${PLUGIN_NAME}.so : ${PLUGIN_NAME}.c + $(CROSS_COMPILE)$(CC) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) $(PLUGIN_LDFLAGS) -fPIC -shared $< -o $@ + +reallyclean : clean +clean: + -rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno + +check: test +test: + +install: ${PLUGIN_NAME}.so + # Don't install, these are examples only. + #$(INSTALL) -d "${DESTDIR}$(libdir)" + #$(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${libdir}/${PLUGIN_NAME}.so" + +uninstall : + -rm -f "${DESTDIR}${libdir}/${PLUGIN_NAME}.so" diff --git a/plugins/examples/topic-jail/mosquitto_topic_jail.c b/plugins/examples/topic-jail/mosquitto_topic_jail.c new file mode 100644 index 00000000..f6c59f8e --- /dev/null +++ b/plugins/examples/topic-jail/mosquitto_topic_jail.c @@ -0,0 +1,257 @@ +/* +Copyright (c) 2020-2021 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License 2.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + https://www.eclipse.org/legal/epl-2.0/ +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +Contributors: + Abilio Marques - initial implementation and documentation. +*/ + +/* + * This is an *example* plugin which demonstrates how to jail a client. + * It mounts such client topics in a subtree starting with the client id. + * It modifies it's subscriptions and the topics of the messages destined to + * that client. It also modifies the topics of the messages published + * by the client. + * + * client | event | destination | original topic | modified topic + * -------|-------|-------------|----------------------|-------------------- + * jailed | sub | --- | topic | ${jailed_id}/topic + * normal | pub | jailed | ${jailed_id}/topic | topic + * jailed | pub | normal | topic | ${jailed_id}/topic + * + * For simplicity of this example, all clients with id starting with "jailed" + * will be jailed. All other clients will work as normal. + * + * Two jailed clients cannot interact with each other. Normal clients can interact + * with any jailed client by publishing or subscribing to the mounted topic. + * + * You should be very sure of what you are doing before making use of this feature. + * + * Compile with: + * gcc -I -fPIC -shared mosquitto_topic_jail.c -o mosquitto_topic_jail.so + * + * Use in config with: + * + * plugin /path/to/mosquitto_topic_jail.so + * + * Note that this only works on Mosquitto 2.0 or later. + */ +#include +#include + +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mosquitto.h" +#include "mqtt_protocol.h" + +#define PLUGIN_NAME "topic-jail" +#define PLUGIN_VERSION "1.0" + +#define UNUSED(A) (void)(A) + +MOSQUITTO_PLUGIN_DECLARE_VERSION(5); + +static mosquitto_plugin_id_t *mosq_pid = NULL; + +static bool is_jailed(const char *str) +{ + return strncmp("jailed", str, 6) == 0; +} + + +static int callback_message_write(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_message *ed = event_data; + char *new_topic; + size_t new_topic_len; + + UNUSED(event); + UNUSED(userdata); + + const char *client_id = mosquitto_client_id(ed->client); + + if(!is_jailed(client_id)){ + /* will only modify the topic of jailed clients */ + return MOSQ_ERR_SUCCESS; + } + + /* put the client_id on front of the topic */ + + /* calculate the length of the new payload */ + new_topic_len = strlen(client_id) + sizeof('/') + strlen(ed->topic) + 1; + + /* Allocate some memory - use + * mosquitto_calloc/mosquitto_malloc/mosquitto_strdup when allocating, to + * allow the broker to track memory usage */ + new_topic = mosquitto_calloc(1, new_topic_len); + if(new_topic == NULL){ + return MOSQ_ERR_NOMEM; + } + + /* prepend the client_id to the topic */ + snprintf(new_topic, new_topic_len, "%s/%s", client_id, ed->topic); + + /* Assign the new topic to the event data structure. You + * must *not* free the original topic, it will be handled by the + * broker. */ + ed->topic = new_topic; + + return MOSQ_ERR_SUCCESS; +} + +static int callback_message_read(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_message *ed = event_data; + size_t client_id_len; + + UNUSED(event); + UNUSED(userdata); + + const char *client_id = mosquitto_client_id(ed->client); + + if(!is_jailed(client_id)){ + /* will only modify the topic of jailed clients */ + return MOSQ_ERR_SUCCESS; + } + + /* remove the client_id from the front of the topic */ + client_id_len = strlen(client_id); + + if(strlen(ed->topic) <= client_id_len + 1){ + /* the topic is not long enough to contain the + * client_id + '/' */ + return MOSQ_ERR_SUCCESS; + } + + if(!strncmp(client_id, ed->topic, client_id_len) && ed->topic[client_id_len] == '/'){ + /* Allocate some memory - use + * mosquitto_calloc/mosquitto_malloc/mosquitto_strdup when allocating, to + * allow the broker to track memory usage */ + + /* skip the client_id + '/' */ + char *new_topic = mosquitto_strdup(ed->topic + client_id_len + 1); + + if(new_topic == NULL){ + return MOSQ_ERR_NOMEM; + } + + /* Assign the new topic to the event data structure. You + * must *not* free the original topic, it will be handled by the + * broker. */ + ed->topic = new_topic; + } + + return MOSQ_ERR_SUCCESS; +} + +static int callback_subscribe(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_subscribe *ed = event_data; + char *new_sub; + size_t new_sub_len; + + UNUSED(event); + UNUSED(userdata); + + const char *client_id = mosquitto_client_id(ed->client); + + if(!is_jailed(client_id)){ + /* will only modify the topic of jailed clients */ + return MOSQ_ERR_SUCCESS; + } + + /* put the client_id on front of the topic */ + + /* calculate the length of the new payload */ + new_sub_len = strlen(client_id) + sizeof('/') + strlen(ed->topic) + 1; + + /* Allocate some memory - use + * mosquitto_calloc/mosquitto_malloc/mosquitto_strdup when allocating, to + * allow the broker to track memory usage */ + new_sub = mosquitto_calloc(1, new_sub_len); + if(new_sub == NULL){ + return MOSQ_ERR_NOMEM; + } + + /* prepend the client_id to the subscription */ + snprintf(new_sub, new_sub_len, "%s/%s", client_id, ed->topic); + + /* Assign the new topic to the event data structure. You + * must *not* free the original topic, it will be handled by the + * broker. */ + ed->topic = new_sub; + + return MOSQ_ERR_SUCCESS; +} + +static int callback_unsubscribe(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_unsubscribe *ed = event_data; + char *new_sub; + size_t new_sub_len; + + UNUSED(event); + UNUSED(userdata); + + const char *client_id = mosquitto_client_id(ed->client); + + if(!is_jailed(client_id)){ + /* will only modify the topic of jailed clients */ + return MOSQ_ERR_SUCCESS; + } + + /* put the client_id on front of the topic */ + + /* calculate the length of the new payload */ + new_sub_len = strlen(client_id) + sizeof('/') + strlen(ed->topic) + 1; + + /* Allocate some memory - use + * mosquitto_calloc/mosquitto_malloc/mosquitto_strdup when allocating, to + * allow the broker to track memory usage */ + new_sub = mosquitto_calloc(1, new_sub_len); + if(new_sub == NULL){ + return MOSQ_ERR_NOMEM; + } + + /* prepend the client_id to the subscription */ + snprintf(new_sub, new_sub_len, "%s/%s", client_id, ed->topic); + + /* Assign the new topic to the event data structure. You + * must *not* free the original topic, it will be handled by the + * broker. */ + ed->topic = new_sub; + + return MOSQ_ERR_SUCCESS; +} + + +int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count) +{ + UNUSED(user_data); + UNUSED(opts); + UNUSED(opt_count); + + mosq_pid = identifier; + mosquitto_plugin_set_info(identifier, PLUGIN_NAME, PLUGIN_VERSION); + + int rc; + + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_MESSAGE_WRITE, callback_message_write, NULL, NULL); + if(rc) return rc; + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_MESSAGE_READ, callback_message_read, NULL, NULL); + if(rc) return rc; + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_SUBSCRIBE, callback_subscribe, NULL, NULL); + if(rc) return rc; + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_UNSUBSCRIBE, callback_unsubscribe, NULL, NULL); + return rc; +} diff --git a/plugins/examples/topic-jail/test.conf b/plugins/examples/topic-jail/test.conf new file mode 100644 index 00000000..542e78f5 --- /dev/null +++ b/plugins/examples/topic-jail/test.conf @@ -0,0 +1 @@ +plugin ./mosquitto_topic_jail.so diff --git a/plugins/examples/topic-jail/test.sh b/plugins/examples/topic-jail/test.sh new file mode 100755 index 00000000..8589ba43 --- /dev/null +++ b/plugins/examples/topic-jail/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +../../../src/mosquitto -c test.conf -v