diff --git a/plugins/README.md b/plugins/README.md index 523d771c..27c7430e 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -68,3 +68,10 @@ to subscribers. This plugin removes the `/uplink` end part of topics that match the pattern `device/+/data/uplink`, so devices publishing to `device/0001/data/uplink` will effectively be publishing to `device/0001/data`. + +## Examples / Wildcard temp +This is an **example** plugin that denies access to the `#` subscription topic +only. This prevents clients from discovering which topics are active and +reduces outgoing bandwidth. If clients connect with username `wildcard` and +subscribes to `#` they will be allowed 20 seconds of access, after which the +subscription will be silently removed. diff --git a/plugins/examples/Makefile b/plugins/examples/Makefile index bf208d59..59bb6c40 100644 --- a/plugins/examples/Makefile +++ b/plugins/examples/Makefile @@ -8,7 +8,8 @@ DIRS= \ message-timestamp \ payload-modification \ print-ip-on-publish \ - topic-modification + topic-modification \ + wildcard-temp .PHONY : all binary check clean reallyclean test install uninstall diff --git a/plugins/examples/wildcard-temp/CMakeLists.txt b/plugins/examples/wildcard-temp/CMakeLists.txt new file mode 100644 index 00000000..fffbaeb8 --- /dev/null +++ b/plugins/examples/wildcard-temp/CMakeLists.txt @@ -0,0 +1,30 @@ +set (PLUGIN_NAME mosquitto_wildcard_temp) + +add_library(${PLUGIN_NAME} MODULE + ${PLUGIN_NAME}.c +) + +target_include_directories(${PLUGIN_NAME} PRIVATE + "${STDBOOL_H_PATH}" + "${STDINT_H_PATH}" + "${mosquitto_SOURCE_DIR}" + "${mosquitto_SOURCE_DIR}/include" +) + +if(WITH_BUNDLED_DEPS) + target_include_directories(${PLUGIN_NAME} PRIVATE + "${${PLUGIN_NAME}}/deps" + ) +endif() + +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/wildcard-temp/Makefile b/plugins/examples/wildcard-temp/Makefile new file mode 100644 index 00000000..7e76722e --- /dev/null +++ b/plugins/examples/wildcard-temp/Makefile @@ -0,0 +1,31 @@ +include ../../../config.mk + +.PHONY : all binary check clean reallyclean test install uninstall + +PLUGIN_NAME=mosquitto_wildcard_temp +PLUGIN_CPPFLAGS+=-I../../../include -I../../../ +ifeq ($(WITH_BUNDLED_DEPS),yes) +PLUGIN_CPPFLAGS+=-I../../../deps +endif + +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/wildcard-temp/mosquitto_wildcard_temp.c b/plugins/examples/wildcard-temp/mosquitto_wildcard_temp.c new file mode 100644 index 00000000..100fe0f0 --- /dev/null +++ b/plugins/examples/wildcard-temp/mosquitto_wildcard_temp.c @@ -0,0 +1,212 @@ +/* +Copyright (c) 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 EDL-1.0 + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +/* + * This is an example plugin showing how to carry out delayed authentication. + * The "authentication" in this example makes no checks whatsoever, but delays + * the response by 5 seconds, and randomly chooses whether it should succeed. + * + * Compile with: + * gcc -I -fPIC -shared mosquitto_delayed_auth.c -o mosquitto_delayed_auth.so + * + * Use in config with: + * + * plugin /path/to/mosquitto_delayed_auth.so + * + * Note that this only works on Mosquitto 2.0 or later. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mosquitto.h" +#include "mqtt_protocol.h" + +#define PLUGIN_NAME "wildcard-temp" +#define PLUGIN_VERSION "1.0" + +#ifndef UNUSED +# define UNUSED(A) (void)(A) +#endif + +MOSQUITTO_PLUGIN_DECLARE_VERSION(5); + +struct client_list{ + UT_hash_handle hh; + struct client_list *next, *prev; + time_t sub_end; + uint8_t sub_status; + char id[]; +}; + +static mosquitto_plugin_id_t *mosq_pid = NULL; +static struct client_list *clients = NULL; +static struct client_list *active_subs = NULL; + +static int connect_callback(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_basic_auth *ed = event_data; + static struct client_list *client; + const char *id, *username; + size_t idlen; + + UNUSED(event); + UNUSED(userdata); + + username = mosquitto_client_username(ed->client); + if(!username || strcmp(username, "wildcard")){ + return MOSQ_ERR_SUCCESS; + } + + id = mosquitto_client_id(ed->client); + idlen = strlen(id); + + HASH_FIND(hh, clients, id, idlen, client); + if(client){ + return MOSQ_ERR_SUCCESS; + }else{ + client = mosquitto_calloc(1, sizeof(struct client_list) + idlen+1); + if(client == NULL){ + return MOSQ_ERR_NOMEM; + } + + memcpy(client->id, id, idlen); + HASH_ADD_KEYPTR(hh, clients, client->id, idlen, client); + } + + return MOSQ_ERR_SUCCESS; +} + + +static int disconnect_callback(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_basic_auth *ed = event_data; + static struct client_list *client; + const char *id; + size_t idlen; + + UNUSED(event); + UNUSED(userdata); + + id = mosquitto_client_id(ed->client); + idlen = strlen(id); + + HASH_FIND(hh, clients, id, idlen, client); + if(client){ + HASH_DELETE(hh, clients, client); + mosquitto_free(client); + } + + return MOSQ_ERR_SUCCESS; +} + + +static int acl_check_callback(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_acl_check *ed = event_data; + static struct client_list *client; + const char *id; + + UNUSED(event); + UNUSED(userdata); + + if(ed->access == MOSQ_ACL_SUBSCRIBE && !strcmp(ed->topic, "#")){ + id = mosquitto_client_id(ed->client); + HASH_FIND(hh, clients, id, strlen(id), client); + if(client && client->sub_status == 0){ + client->sub_status = 1; + client->sub_end = time(NULL) + 20; + DL_APPEND(active_subs, client); + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_ACL_DENIED; + } + } + + return MOSQ_ERR_PLUGIN_IGNORE; +} + + +static int tick_callback(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_tick *ed = event_data; + struct client_list *client, *client_tmp; + time_t now; + + UNUSED(event); + UNUSED(userdata); + + now = time(NULL); + DL_FOREACH_SAFE(active_subs, client, client_tmp){ + if(client->sub_end < now){ + mosquitto_subscription_delete(client->id, "#"); + DL_DELETE(active_subs, client); + }else{ + break; + } + } + + /* Declare that we want another call in at most 1 second */ + ed->next_s = 1; + + return MOSQ_ERR_SUCCESS; +} + + +int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count) +{ + int rc; + + UNUSED(user_data); + UNUSED(opts); + UNUSED(opt_count); + + mosq_pid = identifier; + mosquitto_plugin_set_info(identifier, PLUGIN_NAME, PLUGIN_VERSION); + + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_CONNECT, connect_callback, NULL, NULL); + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_DISCONNECT, disconnect_callback, NULL, NULL); + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_ACL_CHECK, acl_check_callback, NULL, NULL); + if(rc) return rc; + rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_TICK, tick_callback, NULL, NULL); + return rc; +} + +int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int opt_count) +{ + struct client_list *client, *client_tmp; + + UNUSED(user_data); + UNUSED(opts); + UNUSED(opt_count); + + HASH_ITER(hh, clients, client, client_tmp){ + HASH_DELETE(hh, clients, client); + mosquitto_free(client); + } + + return MOSQ_ERR_SUCCESS; +} diff --git a/plugins/examples/wildcard-temp/test.conf b/plugins/examples/wildcard-temp/test.conf new file mode 100644 index 00000000..42c2d9f7 --- /dev/null +++ b/plugins/examples/wildcard-temp/test.conf @@ -0,0 +1,8 @@ +listener 1883 + +plugin ./mosquitto_wildcard_temp.so +allow_anonymous true + + +sys_interval 1 +log_timestamp_format %Y-%m-%dT%H:%M:%S diff --git a/plugins/examples/wildcard-temp/test.sh b/plugins/examples/wildcard-temp/test.sh new file mode 100755 index 00000000..8589ba43 --- /dev/null +++ b/plugins/examples/wildcard-temp/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +../../../src/mosquitto -c test.conf -v