diff --git a/ChangeLog.txt b/ChangeLog.txt index 387777e7..7ace1882 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -101,6 +101,8 @@ Plugins / plugin interface: is present, including a set of default roles. - Add `mosquitto_set_clientid()` to allow plugins to force a client id for a client. +- The dynamic security plugin now only kicks clients at the start of the next + network loop, to give chance for PUBACK/PUBREC to be sent. Closes #2474. Client library: - Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair diff --git a/plugins/dynamic-security/CMakeLists.txt b/plugins/dynamic-security/CMakeLists.txt index 4ad8c0ee..6f726186 100644 --- a/plugins/dynamic-security/CMakeLists.txt +++ b/plugins/dynamic-security/CMakeLists.txt @@ -32,12 +32,14 @@ if(CJSON_FOUND AND WITH_TLS) grouplist.c hash.c ../../common/json_help.c ../../common/json_help.h + kicklist.c ../../common/misc_mosq.c ../../common/misc_mosq.h ../../common/password_mosq.c ../../common/password_mosq.h plugin.c ../common/plugin_common.c ../common/plugin_common.h roles.c rolelist.c + tick.c ) target_include_directories(mosquitto_dynamic_security PRIVATE diff --git a/plugins/dynamic-security/Makefile b/plugins/dynamic-security/Makefile index 53f506ae..84116fac 100644 --- a/plugins/dynamic-security/Makefile +++ b/plugins/dynamic-security/Makefile @@ -23,12 +23,14 @@ OBJS= \ grouplist.o \ hash.o \ json_help.o \ + kicklist.o \ misc_mosq.o \ password_mosq.o \ plugin.o \ plugin_common.o \ roles.o \ - rolelist.o + rolelist.o \ + tick.o ifeq ($(WITH_CJSON),yes) ifeq ($(WITH_TLS),yes) @@ -85,6 +87,9 @@ hash.o : hash.c dynamic_security.h json_help.o : ${R}/common/json_help.c ${R}/common/json_help.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ +kicklist.o : kicklist.c dynamic_security.h + ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ + misc_mosq.o : ${R}/common/misc_mosq.c ${R}/common/misc_mosq.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ @@ -103,6 +108,9 @@ roles.o : roles.c dynamic_security.h rolelist.o : rolelist.c dynamic_security.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ +tick.o : tick.c dynamic_security.h + ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ + reallyclean : clean clean: -rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno diff --git a/plugins/dynamic-security/clientlist.c b/plugins/dynamic-security/clientlist.c index 2a192550..e8fcef2d 100644 --- a/plugins/dynamic-security/clientlist.c +++ b/plugins/dynamic-security/clientlist.c @@ -62,12 +62,12 @@ static int dynsec_clientlist__cmp(void *a, void *b) } -void dynsec_clientlist__kick_all(struct dynsec__clientlist *base_clientlist) +void dynsec_clientlist__kick_all(struct dynsec__data *data, struct dynsec__clientlist *base_clientlist) { struct dynsec__clientlist *clientlist, *clientlist_tmp; HASH_ITER(hh, base_clientlist, clientlist, clientlist_tmp){ - mosquitto_kick_client_by_username(clientlist->client->username, false); + dynsec_kicklist__add(data, clientlist->client->username); } } diff --git a/plugins/dynamic-security/clients.c b/plugins/dynamic-security/clients.c index 53636100..fc3f176d 100644 --- a/plugins/dynamic-security/clients.c +++ b/plugins/dynamic-security/clients.c @@ -492,7 +492,7 @@ int dynsec_clients__process_delete(struct dynsec__data *data, struct plugin_cmd plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); @@ -529,7 +529,7 @@ int dynsec_clients__process_disable(struct dynsec__data *data, struct plugin_cmd client->disabled = true; - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); dynsec__config_save(data); plugin__command_reply(cmd, NULL); @@ -629,7 +629,7 @@ int dynsec_clients__process_set_id(struct dynsec__data *data, struct plugin_cmd plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); @@ -689,7 +689,7 @@ int dynsec_clients__process_set_password(struct dynsec__data *data, struct plugi plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); @@ -769,7 +769,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd rc = client__set_password(client, password); if(rc != MOSQ_ERR_SUCCESS){ plugin__command_reply(cmd, "Internal error"); - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return MOSQ_ERR_NOMEM; } } @@ -779,7 +779,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd str = mosquitto_strdup(text_name); if(str == NULL){ plugin__command_reply(cmd, "Internal error"); - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return MOSQ_ERR_NOMEM; } mosquitto_free(client->text_name); @@ -790,7 +790,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd str = mosquitto_strdup(text_description); if(str == NULL){ plugin__command_reply(cmd, "Internal error"); - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return MOSQ_ERR_NOMEM; } mosquitto_free(client->text_description); @@ -807,7 +807,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd }else if(rc == MOSQ_ERR_NOT_FOUND){ plugin__command_reply(cmd, "Role not found"); dynsec_rolelist__cleanup(&rolelist); - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return MOSQ_ERR_INVAL; }else{ if(rc == MOSQ_ERR_INVAL){ @@ -816,7 +816,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd plugin__command_reply(cmd, "Internal error"); } dynsec_rolelist__cleanup(&rolelist); - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return MOSQ_ERR_INVAL; } @@ -839,7 +839,7 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); @@ -1122,7 +1122,7 @@ int dynsec_clients__process_add_role(struct dynsec__data *data, struct plugin_cm plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); @@ -1176,7 +1176,7 @@ int dynsec_clients__process_remove_role(struct dynsec__data *data, struct plugin plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); diff --git a/plugins/dynamic-security/dynamic_security.h b/plugins/dynamic-security/dynamic_security.h index 87e5f4f4..8170fed1 100644 --- a/plugins/dynamic-security/dynamic_security.h +++ b/plugins/dynamic-security/dynamic_security.h @@ -75,6 +75,11 @@ struct dynsec__rolelist{ int priority; }; +struct dynsec__kicklist{ + struct dynsec__kicklist *next, *prev; + char username[]; +}; + struct dynsec__client{ UT_hash_handle hh; struct mosquitto_pw pw; @@ -137,6 +142,7 @@ struct dynsec__data{ struct dynsec__group *groups; struct dynsec__role *roles; struct dynsec__group *anonymous_group; + struct dynsec__kicklist *kicklist; struct dynsec__acl_default_access default_access; }; @@ -207,7 +213,7 @@ cJSON *dynsec_clientlist__all_to_json(struct dynsec__clientlist *base_clientlist int dynsec_clientlist__add(struct dynsec__clientlist **base_clientlist, struct dynsec__client *client, int priority); void dynsec_clientlist__cleanup(struct dynsec__clientlist **base_clientlist); void dynsec_clientlist__remove(struct dynsec__clientlist **base_clientlist, struct dynsec__client *client); -void dynsec_clientlist__kick_all(struct dynsec__clientlist *base_clientlist); +void dynsec_clientlist__kick_all(struct dynsec__data *data, struct dynsec__clientlist *base_clientlist); /* ################################################################ @@ -280,4 +286,14 @@ int dynsec_rolelist__load_from_json(struct dynsec__data *data, cJSON *command, s void dynsec_rolelist__cleanup(struct dynsec__rolelist **base_rolelist); cJSON *dynsec_rolelist__all_to_json(struct dynsec__rolelist *base_rolelist); +/* ################################################################ + * # + * # Kick List Functions + * # + * ################################################################ */ + +int dynsec_kicklist__add(struct dynsec__data *data, const char *username); +void dynsec_kicklist__kick(struct dynsec__data *data); +int dynsec__tick_callback(int event, void *event_data, void *userdata); + #endif diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index 827c522d..0cfe73c3 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -60,9 +60,9 @@ static cJSON *add_group_to_json(struct dynsec__group *group); static void group__kick_all(struct dynsec__data *data, struct dynsec__group *group) { if(group == data->anonymous_group){ - mosquitto_kick_client_by_username(NULL, false); + dynsec_kicklist__add(data, NULL); } - dynsec_clientlist__kick_all(group->clientlist); + dynsec_clientlist__kick_all(data, group->clientlist); } @@ -569,7 +569,7 @@ int dynsec_groups__process_add_client(struct dynsec__data *data, struct plugin_c } /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return rc; } @@ -666,7 +666,7 @@ int dynsec_groups__process_remove_client(struct dynsec__data *data, struct plugi } /* Enforce any changes */ - mosquitto_kick_client_by_username(username, false); + dynsec_kicklist__add(data, username); return rc; } @@ -1031,7 +1031,7 @@ int dynsec_groups__process_set_anonymous_group(struct dynsec__data *data, struct plugin__command_reply(cmd, NULL); /* Enforce any changes */ - mosquitto_kick_client_by_username(NULL, false); + dynsec_kicklist__add(data, NULL); admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); diff --git a/plugins/dynamic-security/kicklist.c b/plugins/dynamic-security/kicklist.c new file mode 100644 index 00000000..fd06e081 --- /dev/null +++ b/plugins/dynamic-security/kicklist.c @@ -0,0 +1,66 @@ +/* +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: + Roger Light - initial implementation and documentation. +*/ + +#include "config.h" + +#include +#include + +#include "mosquitto.h" +#include "mosquitto_broker.h" + +#include "dynamic_security.h" + +int dynsec_kicklist__add(struct dynsec__data *data, const char *username) +{ + struct dynsec__kicklist *kick; + size_t slen; + + if(username){ + slen = strlen(username); + }else{ + slen = 0; + } + kick = malloc(sizeof(struct dynsec__kicklist)+slen+1); + if(!kick){ + return MOSQ_ERR_NOMEM; + } + if(username){ + strcpy(kick->username, username); + }else{ + kick->username[0] = '\0'; + } + DL_APPEND(data->kicklist, kick); + + return MOSQ_ERR_SUCCESS; +} + +void dynsec_kicklist__kick(struct dynsec__data *data) +{ + struct dynsec__kicklist *kick, *tmp; + + DL_FOREACH_SAFE(data->kicklist, kick, tmp){ + DL_DELETE(data->kicklist, kick); + if(strlen(kick->username)){ + mosquitto_kick_client_by_username(kick->username, false); + }else{ + mosquitto_kick_client_by_username(NULL, false); + } + free(kick); + } +} diff --git a/plugins/dynamic-security/plugin.c b/plugins/dynamic-security/plugin.c index 70e504ea..216ccbb7 100644 --- a/plugins/dynamic-security/plugin.c +++ b/plugins/dynamic-security/plugin.c @@ -65,6 +65,7 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, s mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", &dynsec_data); mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, &dynsec_data); mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, &dynsec_data); + mosquitto_callback_register(plg_id, MOSQ_EVT_TICK, dynsec__tick_callback, NULL, &dynsec_data); return MOSQ_ERR_SUCCESS; } diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c index 35cd0c6f..c058b845 100644 --- a/plugins/dynamic-security/roles.c +++ b/plugins/dynamic-security/roles.c @@ -31,7 +31,7 @@ Contributors: static cJSON *add_role_to_json(struct dynsec__role *role, bool verbose); -static void role__remove_all_clients(struct dynsec__role *role); +static void role__remove_all_clients(struct dynsec__data *data, struct dynsec__role *role); /* ################################################################ * # @@ -108,13 +108,13 @@ static void role__kick_all(struct dynsec__data *data, struct dynsec__role *role) { struct dynsec__grouplist *grouplist, *grouplist_tmp = NULL; - dynsec_clientlist__kick_all(role->clientlist); + dynsec_clientlist__kick_all(data, role->clientlist); HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){ if(grouplist->group == data->anonymous_group){ - mosquitto_kick_client_by_username(NULL, false); + dynsec_kicklist__add(data, NULL); } - dynsec_clientlist__kick_all(grouplist->group->clientlist); + dynsec_clientlist__kick_all(data, grouplist->group->clientlist); } } @@ -437,12 +437,12 @@ error: } -static void role__remove_all_clients(struct dynsec__role *role) +static void role__remove_all_clients(struct dynsec__data *data, struct dynsec__role *role) { struct dynsec__clientlist *clientlist, *clientlist_tmp = NULL; HASH_ITER(hh, role->clientlist, clientlist, clientlist_tmp){ - mosquitto_kick_client_by_username(clientlist->client->username, false); + dynsec_kicklist__add(data, clientlist->client->username); dynsec_rolelist__client_remove(clientlist->client, role); } @@ -454,9 +454,9 @@ static void role__remove_all_groups(struct dynsec__data *data, struct dynsec__ro HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){ if(grouplist->group == data->anonymous_group){ - mosquitto_kick_client_by_username(NULL, false); + dynsec_kicklist__add(data, NULL); } - dynsec_clientlist__kick_all(grouplist->group->clientlist); + dynsec_clientlist__kick_all(data, grouplist->group->clientlist); dynsec_rolelist__group_remove(grouplist->group, role); } @@ -479,7 +479,7 @@ int dynsec_roles__process_delete(struct dynsec__data *data, struct plugin_cmd *c role = dynsec_roles__find(data, rolename); if(role){ - role__remove_all_clients(role); + role__remove_all_clients(data, role); role__remove_all_groups(data, role); role__free_item(data, role, true); dynsec__config_save(data); diff --git a/plugins/dynamic-security/tick.c b/plugins/dynamic-security/tick.c new file mode 100644 index 00000000..211465d3 --- /dev/null +++ b/plugins/dynamic-security/tick.c @@ -0,0 +1,35 @@ +/* +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: + Roger Light - initial implementation and documentation. +*/ + +#include "config.h" + +#include "mosquitto.h" +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mqtt_protocol.h" + +#include "dynamic_security.h" + +int dynsec__tick_callback(int event, void *event_data, void *userdata) +{ + UNUSED(event); + UNUSED(event_data); + + dynsec_kicklist__kick(userdata); + return MOSQ_ERR_SUCCESS; +}