From 3486f0e546a2a1aa541e44ddbbcb4b83f182e38d Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Tue, 3 Nov 2020 18:16:44 +0000 Subject: [PATCH] Dynsec: Kick affected clients on role change. --- plugins/dynamic-security/clients.c | 46 ++++-- plugins/dynamic-security/dynamic_security.h | 13 +- plugins/dynamic-security/groups.c | 44 ++---- plugins/dynamic-security/roles.c | 159 +++++++++++++++++++- 4 files changed, 206 insertions(+), 56 deletions(-) diff --git a/plugins/dynamic-security/clients.c b/plugins/dynamic-security/clients.c index a01ec141..df024d3e 100644 --- a/plugins/dynamic-security/clients.c +++ b/plugins/dynamic-security/clients.c @@ -49,6 +49,14 @@ static struct dynsec__client *local_clients = NULL; * # * ################################################################ */ +int dynsec_clientlist__cmp(void *a, void *b) +{ + struct dynsec__clientlist *clientlist_a = a; + struct dynsec__clientlist *clientlist_b = b; + + return strcmp(clientlist_a->username, clientlist_b->username); +} + static int client_cmp(void *a, void *b) { struct dynsec__client *client_a = a; @@ -243,7 +251,7 @@ int dynsec_clients__config_load(cJSON *tree) if(jtmp && cJSON_IsString(jtmp)){ json_get_int(j_role, "priority", &priority, true, -1); role = dynsec_roles__find(jtmp->valuestring); - dynsec_rolelists__add_role(&client->rolelist, role, priority); + dynsec_rolelists__client_add_role(client, role, priority); } } } @@ -562,6 +570,23 @@ int dynsec_clients__process_set_password(cJSON *j_responses, struct mosquitto *c } +static void client__add_new_roles(struct dynsec__client *client, struct dynsec__rolelist *base_rolelist) +{ + struct dynsec__rolelist *rolelist, *rolelist_tmp; + + HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){ + dynsec_rolelists__client_add_role(client, rolelist->role, rolelist->priority); + } +} + +static void client__remove_all_roles(struct dynsec__client *client) +{ + struct dynsec__rolelist *rolelist, *rolelist_tmp; + + HASH_ITER(hh, client->rolelist, rolelist, rolelist_tmp){ + dynsec_rolelists__client_remove_role(client, rolelist->role); + } +} int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { @@ -607,8 +632,9 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context rc = dynsec_rolelists__load_from_json(command, &rolelist); if(rc == MOSQ_ERR_SUCCESS){ - dynsec_rolelists__free_all(&client->rolelist); - client->rolelist = rolelist; + client__remove_all_roles(client); + client__add_new_roles(client, rolelist); + dynsec_rolelists__free_all(&rolelist); }else if(rc == MOSQ_ERR_NOT_FOUND){ dynsec__command_reply(j_responses, context, "modifyClient", "Role not found", correlation_data); dynsec_rolelists__free_all(&rolelist); @@ -848,16 +874,6 @@ int dynsec_clients__process_list(cJSON *j_responses, struct mosquitto *context, } -void dynsec_clients__remove_role_from_all(const struct dynsec__role *role) -{ - struct dynsec__client *client, *client_tmp; - - HASH_ITER(hh, local_clients, client, client_tmp){ - dynsec_rolelists__remove_role(&client->rolelist, role); - } -} - - int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *role_name; @@ -888,7 +904,7 @@ int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *conte return MOSQ_ERR_SUCCESS; } - dynsec_rolelists__add_role(&client->rolelist, role, priority); + dynsec_rolelists__client_add_role(client, role, priority); dynsec__config_save(); dynsec__command_reply(j_responses, context, "addClientRole", NULL, correlation_data); @@ -927,7 +943,7 @@ int dynsec_clients__process_remove_role(cJSON *j_responses, struct mosquitto *co return MOSQ_ERR_SUCCESS; } - dynsec_rolelists__remove_role(&client->rolelist, role); + dynsec_rolelists__client_remove_role(client, role); dynsec__config_save(); dynsec__command_reply(j_responses, context, "removeClientRole", NULL, correlation_data); diff --git a/plugins/dynamic-security/dynamic_security.h b/plugins/dynamic-security/dynamic_security.h index 6e8a0aa2..8ddcc893 100644 --- a/plugins/dynamic-security/dynamic_security.h +++ b/plugins/dynamic-security/dynamic_security.h @@ -114,6 +114,8 @@ struct dynsec__acls{ struct dynsec__role{ UT_hash_handle hh; struct dynsec__acls acls; + struct dynsec__clientlist *clientlist; + struct dynsec__grouplist *grouplist; char *rolename; char *text_name; char *text_description; @@ -182,9 +184,10 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context int dynsec_clients__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); int dynsec_clients__process_set_password(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); struct dynsec__client *dynsec_clients__find(const char *username); -void dynsec_clients__remove_role_from_all(const struct dynsec__role *role); cJSON *dynsec_clientlists__all_to_json(struct dynsec__clientlist *base_clientlist); +int dynsec_clientlist__cmp(void *a, void *b); +void dynsec_clientlist__kick_all(struct dynsec__clientlist *base_clientlist); @@ -211,9 +214,9 @@ int dynsec_groups__process_get_anonymous_group(cJSON *j_responses, struct mosqui int dynsec_groups__process_set_anonymous_group(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); int dynsec_groups__remove_client(const char *username, const char *groupname, bool update_config); struct dynsec__group *dynsec_groups__find(const char *groupname); -void dynsec_groups__remove_role_from_all(const struct dynsec__role *role); cJSON *dynsec_grouplists__all_to_json(struct dynsec__grouplist *base_grouplist); +int dynsec_grouplist__cmp(void *a, void *b); /* ################################################################ * # @@ -233,9 +236,11 @@ int dynsec_roles__process_modify(cJSON *j_responses, struct mosquitto *context, int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); struct dynsec__role *dynsec_roles__find(const char *rolename); -int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority); +int dynsec_rolelists__client_add_role(struct dynsec__client *client, struct dynsec__role *role, int priority); +int dynsec_rolelists__client_remove_role(struct dynsec__client *client, struct dynsec__role *role); +int dynsec_rolelists__group_add_role(struct dynsec__group *group, struct dynsec__role *role, int priority); +int dynsec_rolelists__group_remove_role(struct dynsec__group *group, struct dynsec__role *role); int dynsec_rolelists__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist); -int dynsec_rolelists__remove_role(struct dynsec__rolelist **base_rolelist, const struct dynsec__role *role); void dynsec_rolelists__free_all(struct dynsec__rolelist **base_rolelist); cJSON *dynsec_rolelists__all_to_json(struct dynsec__rolelist *base_rolelist); diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index 1165f0e3..c879ed9f 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -68,7 +68,7 @@ static int group_cmp(void *a, void *b) return strcmp(group_a->groupname, group_b->groupname); } -static int grouplist_cmp(void *a, void *b) +int dynsec_grouplist__cmp(void *a, void *b) { struct dynsec__grouplist *grouplist_a = a; struct dynsec__grouplist *grouplist_b = b; @@ -76,15 +76,7 @@ static int grouplist_cmp(void *a, void *b) return grouplist_b->priority - grouplist_a->priority; } -static int clientlist_cmp(void *a, void *b) -{ - struct dynsec__clientlist *clientlist_a = a; - struct dynsec__clientlist *clientlist_b = b; - - return strcmp(clientlist_a->username, clientlist_b->username); -} - -static void clientlist__kick_all(struct dynsec__clientlist *base_clientlist) +void dynsec_clientlist__kick_all(struct dynsec__clientlist *base_clientlist) { struct dynsec__clientlist *clientlist, *clientlist_tmp; @@ -202,7 +194,7 @@ int dynsec_groups__process_add_role(cJSON *j_responses, struct mosquitto *contex return MOSQ_ERR_SUCCESS; } - dynsec_rolelists__add_role(&group->rolelist, role, priority); + dynsec_rolelists__group_add_role(group, role, priority); dynsec__config_save(); dynsec__command_reply(j_responses, context, "addGroupRole", NULL, correlation_data); return MOSQ_ERR_SUCCESS; @@ -302,7 +294,7 @@ int dynsec_groups__config_load(cJSON *tree) if(j_rolename && cJSON_IsString(j_rolename)){ json_get_int(j_role, "priority", &priority, true, -1); role = dynsec_roles__find(j_rolename->valuestring); - dynsec_rolelists__add_role(&group->rolelist, role, priority); + dynsec_rolelists__group_add_role(group, role, priority); } } } @@ -493,7 +485,7 @@ int dynsec_groups__process_delete(cJSON *j_responses, struct mosquitto *context, if(group == dynsec_anonymous_group){ mosquitto_kick_client_by_username(NULL, false); } - clientlist__kick_all(group->clientlist); + dynsec_clientlist__kick_all(group->clientlist); group__free_item(group); dynsec__config_save(); @@ -541,12 +533,12 @@ int dynsec_groups__add_client(const char *username, const char *groupname, int p clientlist->username = client->username; clientlist->client = client; clientlist->priority = priority; - HASH_ADD_KEYPTR_INORDER(hh, group->clientlist, clientlist->username, strlen(clientlist->username), clientlist, clientlist_cmp); + HASH_ADD_KEYPTR_INORDER(hh, group->clientlist, clientlist->username, strlen(clientlist->username), clientlist, dynsec_clientlist__cmp); grouplist->groupname = group->groupname; grouplist->group = group; grouplist->priority = priority; - HASH_ADD_KEYPTR_INORDER(hh, client->grouplist, grouplist->groupname, strlen(grouplist->groupname), grouplist, grouplist_cmp); + HASH_ADD_KEYPTR_INORDER(hh, client->grouplist, grouplist->groupname, strlen(grouplist->groupname), grouplist, dynsec_grouplist__cmp); if(update_config){ dynsec__config_save(); @@ -882,22 +874,6 @@ int dynsec_groups__process_get(cJSON *j_responses, struct mosquitto *context, cJ } -void dynsec_groups__remove_role_from_all(const struct dynsec__role *role) -{ - struct dynsec__group *group, *group_tmp; - struct dynsec__rolelist *rolelist; - - HASH_ITER(hh, local_groups, group, group_tmp){ - HASH_FIND(hh, group->rolelist, role->rolename, strlen(role->rolename), rolelist); - if(rolelist){ - HASH_DELETE(hh, group->rolelist, rolelist); - mosquitto_free(rolelist->rolename); - mosquitto_free(rolelist); - } - } -} - - int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *groupname, *rolename; @@ -926,7 +902,7 @@ int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *con return MOSQ_ERR_SUCCESS; } - dynsec_rolelists__remove_role(&group->rolelist, role); + dynsec_rolelists__group_remove_role(group, role); dynsec__config_save(); dynsec__command_reply(j_responses, context, "removeGroupRole", NULL, correlation_data); @@ -934,7 +910,7 @@ int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *con if(group == dynsec_anonymous_group){ mosquitto_kick_client_by_username(NULL, false); } - clientlist__kick_all(group->clientlist); + dynsec_clientlist__kick_all(group->clientlist); return MOSQ_ERR_SUCCESS; } @@ -1020,7 +996,7 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, if(group == dynsec_anonymous_group){ mosquitto_kick_client_by_username(NULL, false); } - clientlist__kick_all(group->clientlist); + dynsec_clientlist__kick_all(group->clientlist); return MOSQ_ERR_SUCCESS; } diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c index 19cfb916..81261feb 100644 --- a/plugins/dynamic-security/roles.c +++ b/plugins/dynamic-security/roles.c @@ -92,7 +92,48 @@ int dynsec_rolelists__remove_role(struct dynsec__rolelist **base_rolelist, const } -int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority) +int dynsec_rolelists__client_remove_role(struct dynsec__client *client, struct dynsec__role *role) +{ + int rc; + struct dynsec__clientlist *found_clientlist; + + rc = dynsec_rolelists__remove_role(&client->rolelist, role); + if(rc) return rc; + + HASH_FIND(hh, role->clientlist, client->username, strlen(client->username), found_clientlist); + if(found_clientlist){ + HASH_DELETE(hh, role->clientlist, found_clientlist); + mosquitto_free(found_clientlist->username); + mosquitto_free(found_clientlist); + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_NOT_FOUND; + } +} + + +int dynsec_rolelists__group_remove_role(struct dynsec__group *group, struct dynsec__role *role) +{ + int rc; + struct dynsec__grouplist *found_grouplist; + + rc = dynsec_rolelists__remove_role(&group->rolelist, role); + if(rc) return rc; + + /* Remove group from role grouplist. */ + HASH_FIND(hh, role->grouplist, group->groupname, strlen(group->groupname), found_grouplist); + if(found_grouplist){ + HASH_DELETE(hh, role->grouplist, found_grouplist); + mosquitto_free(found_grouplist->groupname); + mosquitto_free(found_grouplist); + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_NOT_FOUND; + } +} + + +static int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority) { struct dynsec__rolelist *rolelist; @@ -118,6 +159,74 @@ int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct d } +int dynsec_rolelists__client_add_role(struct dynsec__client *client, struct dynsec__role *role, int priority) +{ + struct dynsec__rolelist *rolelist; + struct dynsec__clientlist *clientlist; + int rc; + + rc = dynsec_rolelists__add_role(&client->rolelist, role, priority); + if(rc) return rc; + + HASH_FIND(hh, client->rolelist, role->rolename, strlen(role->rolename), rolelist); + if(rolelist == NULL){ + /* This should never happen because the above add_role succeeded. */ + return MOSQ_ERR_UNKNOWN; + } + + /* Add client to role clientlist */ + clientlist = mosquitto_calloc(1, sizeof(struct dynsec__clientlist)); + if(clientlist == NULL){ + dynsec_rolelists__remove_role(&client->rolelist, role); + return MOSQ_ERR_NOMEM; + } + clientlist->client = client; + clientlist->username = mosquitto_strdup(client->username); + if(clientlist->username == NULL){ + dynsec_rolelists__remove_role(&client->rolelist, role); + mosquitto_free(clientlist); + return MOSQ_ERR_NOMEM; + } + + HASH_ADD_KEYPTR_INORDER(hh, role->clientlist, client->username, strlen(client->username), clientlist, dynsec_clientlist__cmp); + return MOSQ_ERR_SUCCESS; +} + + +int dynsec_rolelists__group_add_role(struct dynsec__group *group, struct dynsec__role *role, int priority) +{ + struct dynsec__rolelist *rolelist; + struct dynsec__grouplist *grouplist; + int rc; + + rc = dynsec_rolelists__add_role(&group->rolelist, role, priority); + if(rc) return rc; + + HASH_FIND(hh, group->rolelist, role->rolename, strlen(role->rolename), rolelist); + if(rolelist == NULL){ + /* This should never happen because the above add_role succeeded. */ + return MOSQ_ERR_UNKNOWN; + } + + /* Add group to role grouplist */ + grouplist = mosquitto_calloc(1, sizeof(struct dynsec__grouplist)); + if(grouplist == NULL){ + dynsec_rolelists__remove_role(&group->rolelist, role); + return MOSQ_ERR_NOMEM; + } + grouplist->group = group; + grouplist->groupname = mosquitto_strdup(group->groupname); + if(grouplist->groupname == NULL){ + dynsec_rolelists__remove_role(&group->rolelist, role); + mosquitto_free(grouplist); + return MOSQ_ERR_NOMEM; + } + + HASH_ADD_KEYPTR_INORDER(hh, role->grouplist, group->groupname, strlen(group->groupname), grouplist, dynsec_grouplist__cmp); + return MOSQ_ERR_SUCCESS; +} + + int dynsec_rolelists__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist) { cJSON *j_roles, *j_role, *j_rolename; @@ -228,6 +337,21 @@ void dynsec_roles__cleanup(void) } +static void role__kick_all(struct dynsec__role *role) +{ + struct dynsec__grouplist *grouplist, *grouplist_tmp; + + dynsec_clientlist__kick_all(role->clientlist); + + HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){ + if(grouplist->group == dynsec_anonymous_group){ + mosquitto_kick_client_by_username(NULL, false); + } + dynsec_clientlist__kick_all(grouplist->group->clientlist); + } +} + + /* ################################################################ * # * # Config file load and save @@ -528,6 +652,31 @@ error: } +static void role__remove_all_clients(struct dynsec__role *role) +{ + struct dynsec__clientlist *clientlist, *clientlist_tmp; + + HASH_ITER(hh, role->clientlist, clientlist, clientlist_tmp){ + mosquitto_kick_client_by_username(clientlist->username, false); + + dynsec_rolelists__client_remove_role(clientlist->client, role); + } +} + +static void role__remove_all_groups(struct dynsec__role *role) +{ + struct dynsec__grouplist *grouplist, *grouplist_tmp; + + HASH_ITER(hh, role->grouplist, grouplist, grouplist_tmp){ + if(grouplist->group == dynsec_anonymous_group){ + mosquitto_kick_client_by_username(NULL, false); + } + dynsec_clientlist__kick_all(grouplist->group->clientlist); + + dynsec_rolelists__group_remove_role(grouplist->group, role); + } +} + int dynsec_roles__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *rolename; @@ -540,8 +689,8 @@ int dynsec_roles__process_delete(cJSON *j_responses, struct mosquitto *context, role = dynsec_roles__find(rolename); if(role){ - dynsec_clients__remove_role_from_all(role); - dynsec_groups__remove_role_from_all(role); + role__remove_all_clients(role); + role__remove_all_groups(role); role__free_item(role, true); dynsec__config_save(); dynsec__command_reply(j_responses, context, "deleteRole", NULL, correlation_data); @@ -736,6 +885,8 @@ int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, dynsec__config_save(); dynsec__command_reply(j_responses, context, "addRoleACL", NULL, correlation_data); + role__kick_all(role); + return MOSQ_ERR_SUCCESS; } @@ -790,6 +941,8 @@ int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *conte role__free_acl(acllist, acl); dynsec__config_save(); dynsec__command_reply(j_responses, context, "removeRoleACL", NULL, correlation_data); + + role__kick_all(role); }else{ dynsec__command_reply(j_responses, context, "removeRoleACL", "ACL not found", correlation_data); }