/* Copyright (c) 2020 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include "mosquitto.h" #include "mosquitto_broker.h" #include "json_help.h" #include "dynamic_security.h" /* ################################################################ * # * # Function declarations * # * ################################################################ */ static int dynsec__remove_client_from_all_groups(const char *username); /* ################################################################ * # * # Local variables * # * ################################################################ */ static struct dynsec__client *local_clients = NULL; /* ################################################################ * # * # Utility functions * # * ################################################################ */ 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; struct dynsec__client *client_b = b; return strcmp(client_a->username, client_b->username); } static void client__free_item(struct dynsec__client *client) { if(client == NULL) return; HASH_DEL(local_clients, client); dynsec_rolelists__free_all(&client->rolelist); dynsec__remove_client_from_all_groups(client->username); mosquitto_free(client->text_name); mosquitto_free(client->text_description); mosquitto_free(client->clientid); mosquitto_free(client->username); mosquitto_free(client); } struct dynsec__client *dynsec_clients__find(const char *username) { struct dynsec__client *client = NULL; if(username){ HASH_FIND(hh, local_clients, username, strlen(username), client); } return client; } void dynsec_clients__cleanup(void) { struct dynsec__client *client, *client_tmp; HASH_ITER(hh, local_clients, client, client_tmp){ client__free_item(client); } } /* ################################################################ * # * # Config file load and save * # * ################################################################ */ int dynsec_clients__config_load(cJSON *tree) { cJSON *j_clients, *j_client, *jtmp, *j_roles, *j_role; cJSON *j_salt, *j_password, *j_iterations; struct dynsec__client *client; struct dynsec__role *role; unsigned char *buf; int buf_len; int priority; int iterations; j_clients = cJSON_GetObjectItem(tree, "clients"); if(j_clients == NULL){ return 0; } if(cJSON_IsArray(j_clients) == false){ return 1; } cJSON_ArrayForEach(j_client, j_clients){ if(cJSON_IsObject(j_client) == true){ client = mosquitto_calloc(1, sizeof(struct dynsec__client)); if(client == NULL){ // FIXME log return MOSQ_ERR_NOMEM; } /* Username */ jtmp = cJSON_GetObjectItem(j_client, "username"); if(jtmp == NULL || !cJSON_IsString(jtmp)){ // FIXME log mosquitto_free(client); continue; } client->username = mosquitto_strdup(jtmp->valuestring); if(client->username == NULL){ // FIXME log mosquitto_free(client); continue; } jtmp = cJSON_GetObjectItem(j_client, "disabled"); if(jtmp && cJSON_IsBool(jtmp)){ client->disabled = cJSON_IsTrue(jtmp); } /* Salt */ j_salt = cJSON_GetObjectItem(j_client, "salt"); j_password = cJSON_GetObjectItem(j_client, "password"); j_iterations = cJSON_GetObjectItem(j_client, "iterations"); if(j_salt && cJSON_IsString(j_salt) && j_password && cJSON_IsString(j_password) && j_iterations && cJSON_IsNumber(j_iterations)){ iterations = (int)j_iterations->valuedouble; if(iterations < 1){ // FIXME log mosquitto_free(client->username); mosquitto_free(client); continue; }else{ client->pw.iterations = iterations; } if(dynsec_auth__base64_decode(j_salt->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS || buf_len != sizeof(client->pw.salt)){ // FIXME log mosquitto_free(client->username); mosquitto_free(client); continue; } memcpy(client->pw.salt, buf, (size_t)buf_len); mosquitto_free(buf); if(dynsec_auth__base64_decode(j_password->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS || buf_len != sizeof(client->pw.password_hash)){ // FIXME log mosquitto_free(client->username); mosquitto_free(client); continue; } memcpy(client->pw.password_hash, buf, (size_t)buf_len); mosquitto_free(buf); client->pw.valid = true; }else{ client->pw.valid = false; } /* Client id */ jtmp = cJSON_GetObjectItem(j_client, "clientid"); if(jtmp != NULL && cJSON_IsString(jtmp)){ client->clientid = mosquitto_strdup(jtmp->valuestring); if(client->clientid == NULL){ // FIXME log mosquitto_free(client->username); mosquitto_free(client); continue; } } /* Text name */ jtmp = cJSON_GetObjectItem(j_client, "textname"); if(jtmp != NULL && cJSON_IsString(jtmp)){ client->text_name = mosquitto_strdup(jtmp->valuestring); if(client->text_name == NULL){ // FIXME log mosquitto_free(client->clientid); mosquitto_free(client->username); mosquitto_free(client); continue; } } /* Text description */ jtmp = cJSON_GetObjectItem(j_client, "textdescription"); if(jtmp != NULL && cJSON_IsString(jtmp)){ client->text_description = mosquitto_strdup(jtmp->valuestring); if(client->text_description == NULL){ // FIXME log mosquitto_free(client->text_name); mosquitto_free(client->clientid); mosquitto_free(client->username); mosquitto_free(client); continue; } } /* Roles */ j_roles = cJSON_GetObjectItem(j_client, "roles"); if(j_roles && cJSON_IsArray(j_roles)){ cJSON_ArrayForEach(j_role, j_roles){ if(cJSON_IsObject(j_role)){ jtmp = cJSON_GetObjectItem(j_role, "rolename"); if(jtmp && cJSON_IsString(jtmp)){ json_get_int(j_role, "priority", &priority, true, -1); role = dynsec_roles__find(jtmp->valuestring); dynsec_rolelists__client_add_role(client, role, priority); } } } } HASH_ADD_KEYPTR(hh, local_clients, client->username, strlen(client->username), client); } } HASH_SORT(local_clients, client_cmp); return 0; } static int dynsec__config_add_clients(cJSON *j_clients) { struct dynsec__client *client, *client_tmp; cJSON *j_client, *j_roles, *jtmp; char *buf; HASH_ITER(hh, local_clients, client, client_tmp){ j_client = cJSON_CreateObject(); if(j_client == NULL) return 1; cJSON_AddItemToArray(j_clients, j_client); if(cJSON_AddStringToObject(j_client, "username", client->username) == NULL || (client->clientid && cJSON_AddStringToObject(j_client, "clientid", client->clientid) == NULL) || (client->text_name && cJSON_AddStringToObject(j_client, "textname", client->text_name) == NULL) || (client->text_description && cJSON_AddStringToObject(j_client, "textdescription", client->text_description) == NULL) || (client->disabled && cJSON_AddBoolToObject(j_client, "disabled", true) == NULL) ){ return 1; } j_roles = dynsec_rolelists__all_to_json(client->rolelist); if(j_roles == NULL){ return 1; } cJSON_AddItemToObject(j_client, "roles", j_roles); if(client->pw.valid){ if(dynsec_auth__base64_encode(client->pw.password_hash, sizeof(client->pw.password_hash), &buf) != MOSQ_ERR_SUCCESS){ return 1; } jtmp = cJSON_CreateString(buf); mosquitto_free(buf); if(jtmp == NULL) return 1; cJSON_AddItemToObject(j_client, "password", jtmp); if(dynsec_auth__base64_encode(client->pw.salt, sizeof(client->pw.salt), &buf) != MOSQ_ERR_SUCCESS){ return 1; } jtmp = cJSON_CreateString(buf); mosquitto_free(buf); if(jtmp == NULL) return 1; cJSON_AddItemToObject(j_client, "salt", jtmp); if(cJSON_AddIntToObject(j_client, "iterations", client->pw.iterations) == NULL){ return 1; } } } return 0; } int dynsec_clients__config_save(cJSON *tree) { cJSON *j_clients; j_clients = cJSON_CreateArray(); if(j_clients == NULL){ return 1; } cJSON_AddItemToObject(tree, "clients", j_clients); if(dynsec__config_add_clients(j_clients)){ return 1; } return 0; } int dynsec_clients__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *password, *clientid = NULL; char *text_name, *text_description; struct dynsec__client *client; int rc; cJSON *j_groups, *j_group, *jtmp; int priority; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "password", &password, true) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Invalid/missing password", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "clientid", &clientid, true) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Invalid/missing client id", correlation_data); return MOSQ_ERR_INVAL; } if(clientid && mosquitto_validate_utf8(clientid, (int)strlen(clientid)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Client ID not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "textname", &text_name, true) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Invalid/missing textname", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "textdescription", &text_description, true) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "createClient", "Invalid/missing textdescription", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client){ dynsec__command_reply(j_responses, context, "createClient", "Client already exists", correlation_data); return MOSQ_ERR_SUCCESS; } client = mosquitto_calloc(1, sizeof(struct dynsec__client)); if(client == NULL){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } client->username = mosquitto_strdup(username); if(client->username == NULL){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_NOMEM; } if(text_name){ client->text_name = mosquitto_strdup(text_name); if(client->text_name == NULL){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_NOMEM; } } if(text_description){ client->text_description = mosquitto_strdup(text_description); if(client->text_description == NULL){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_NOMEM; } } if(password){ if(dynsec_auth__pw_hash(client, password, client->pw.password_hash, sizeof(client->pw.password_hash), true)){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_NOMEM; } client->pw.valid = true; } if(clientid){ client->clientid = mosquitto_strdup(clientid); if(client->clientid == NULL){ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_NOMEM; } } rc = dynsec_rolelists__load_from_json(command, &client->rolelist); if(rc == MOSQ_ERR_SUCCESS || rc == ERR_LIST_NOT_FOUND){ }else if(rc == MOSQ_ERR_NOT_FOUND){ dynsec__command_reply(j_responses, context, "createClient", "Role not found", correlation_data); client__free_item(client); return MOSQ_ERR_INVAL; }else{ dynsec__command_reply(j_responses, context, "createClient", "Internal error", correlation_data); client__free_item(client); return MOSQ_ERR_INVAL; } j_groups = cJSON_GetObjectItem(command, "groups"); if(j_groups && cJSON_IsArray(j_groups)){ cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group)){ jtmp = cJSON_GetObjectItem(j_group, "groupname"); if(jtmp && cJSON_IsString(jtmp)){ json_get_int(j_group, "priority", &priority, true, -1); dynsec_groups__add_client(username, jtmp->valuestring, priority, false); } } } } HASH_ADD_KEYPTR_INORDER(hh, local_clients, client->username, strlen(client->username), client, client_cmp); dynsec__config_save(); dynsec__command_reply(j_responses, context, "createClient", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username; struct dynsec__client *client; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "deleteClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client){ dynsec__remove_client_from_all_groups(username); client__free_item(client); dynsec__config_save(); dynsec__command_reply(j_responses, context, "deleteClient", NULL, correlation_data); /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_SUCCESS; }else{ dynsec__command_reply(j_responses, context, "deleteClient", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } } int dynsec_clients__process_disable(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username; struct dynsec__client *client; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "disableClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "disableClient", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "disableClient", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } client->disabled = true; mosquitto_kick_client_by_username(username, false); dynsec__config_save(); dynsec__command_reply(j_responses, context, "disableClient", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_enable(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username; struct dynsec__client *client; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "enableClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "enableClient", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "enableClient", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } client->disabled = false; dynsec__config_save(); dynsec__command_reply(j_responses, context, "enableClient", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_set_id(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *clientid, *clientid_heap; struct dynsec__client *client; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientId", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientId", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "clientid", &clientid, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientId", "Invalid/missing client ID", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(clientid, (int)strlen(clientid)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientId", "Client ID not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "setClientId", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } if(clientid != NULL && strlen(clientid) > 0){ clientid_heap = mosquitto_strdup(clientid); if(clientid_heap == NULL){ dynsec__command_reply(j_responses, context, "setClientId", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } }else{ clientid_heap = NULL; } mosquitto_free(client->clientid); client->clientid = clientid_heap; /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_SUCCESS; } static int client__set_password(struct dynsec__client *client, const char *password) { if(dynsec_auth__pw_hash(client, password, client->pw.password_hash, sizeof(client->pw.password_hash), true) == MOSQ_ERR_SUCCESS){ client->pw.valid = true; return MOSQ_ERR_SUCCESS; }else{ client->pw.valid = false; // FIXME - this should fail safe without modifying the existing password return MOSQ_ERR_NOMEM; } } int dynsec_clients__process_set_password(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *password; struct dynsec__client *client; int rc; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientPassword", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientPassword", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "password", &password, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "setClientPassword", "Invalid/missing password", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "setClientPassword", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } rc = client__set_password(client, password); if(rc == MOSQ_ERR_SUCCESS){ dynsec__config_save(); dynsec__command_reply(j_responses, context, "setClientPassword", NULL, correlation_data); /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); }else{ dynsec__command_reply(j_responses, context, "setClientPassword", "Internal error", correlation_data); } return rc; } 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) { char *username; char *clientid; char *password; char *text_name, *text_description; struct dynsec__client *client; struct dynsec__rolelist *rolelist = NULL; char *str; int rc; int priority; cJSON *j_group, *j_groups, *jtmp; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "modifyClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "modifyClient", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "modifyClient", "Client does not exist", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){ str = mosquitto_strdup(clientid); if(str == NULL){ dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } mosquitto_free(client->clientid); client->clientid = str; } if(json_get_string(command, "password", &password, false) == MOSQ_ERR_SUCCESS){ rc = client__set_password(client, password); if(rc != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data); mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_NOMEM; } } if(json_get_string(command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){ str = mosquitto_strdup(text_name); if(str == NULL){ dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data); mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_NOMEM; } mosquitto_free(client->text_name); client->text_name = str; } if(json_get_string(command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){ str = mosquitto_strdup(text_description); if(str == NULL){ dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data); mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_NOMEM; } mosquitto_free(client->text_description); client->text_description = str; } rc = dynsec_rolelists__load_from_json(command, &rolelist); if(rc == MOSQ_ERR_SUCCESS){ 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); mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_INVAL; }else if(rc == ERR_LIST_NOT_FOUND){ /* There was no list in the JSON, so no modification */ }else{ dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data); dynsec_rolelists__free_all(&rolelist); mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_INVAL; } j_groups = cJSON_GetObjectItem(command, "groups"); if(j_groups && cJSON_IsArray(j_groups)){ dynsec__remove_client_from_all_groups(username); cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group)){ jtmp = cJSON_GetObjectItem(j_group, "groupname"); if(jtmp && cJSON_IsString(jtmp)){ json_get_int(j_group, "priority", &priority, true, -1); dynsec_groups__add_client(username, jtmp->valuestring, priority, false); } } } } dynsec__config_save(); dynsec__command_reply(j_responses, context, "modifyClient", NULL, correlation_data); /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_SUCCESS; } static int dynsec__remove_client_from_all_groups(const char *username) { struct dynsec__grouplist *grouplist, *grouplist_tmp; struct dynsec__client *client; client = dynsec_clients__find(username); if(client){ HASH_ITER(hh, client->grouplist, grouplist, grouplist_tmp){ dynsec_groups__remove_client(username, grouplist->groupname, false); } } return MOSQ_ERR_SUCCESS; } static cJSON *add_client_to_json(struct dynsec__client *client, bool verbose) { cJSON *j_client = NULL, *j_groups, *j_roles; if(verbose){ j_client = cJSON_CreateObject(); if(j_client == NULL){ return NULL; } if(cJSON_AddStringToObject(j_client, "username", client->username) == NULL || (client->clientid && cJSON_AddStringToObject(j_client, "clientid", client->clientid) == NULL) || (client->text_name && cJSON_AddStringToObject(j_client, "textname", client->text_name) == NULL) || (client->text_description && cJSON_AddStringToObject(j_client, "textdescription", client->text_description) == NULL) || (client->disabled && cJSON_AddBoolToObject(j_client, "disabled", client->disabled) == NULL) ){ cJSON_Delete(j_client); return NULL; } j_roles = dynsec_rolelists__all_to_json(client->rolelist); if(j_roles == NULL){ cJSON_Delete(j_client); return NULL; } cJSON_AddItemToObject(j_client, "roles", j_roles); j_groups = dynsec_grouplists__all_to_json(client->grouplist); if(j_groups == NULL){ cJSON_Delete(j_client); return NULL; } cJSON_AddItemToObject(j_client, "groups", j_groups); }else{ j_client = cJSON_CreateString(client->username); if(j_client == NULL){ return NULL; } } return j_client; } int dynsec_clients__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username; struct dynsec__client *client; cJSON *tree, *j_client, *jtmp, *j_data; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "getClient", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "getClient", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "getClient", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } tree = cJSON_CreateObject(); if(tree == NULL){ dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } jtmp = cJSON_CreateString("getClient"); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(tree, "command", jtmp); j_data = cJSON_CreateObject(); if(j_data == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(tree, "data", j_data); j_client = add_client_to_json(client, true); if(j_client == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(j_data, "client", j_client); if(correlation_data){ jtmp = cJSON_CreateString(correlation_data); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data); return 1; } cJSON_AddItemToObject(tree, "correlationData", jtmp); } cJSON_AddItemToArray(j_responses, tree); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { bool verbose; struct dynsec__client *client, *client_tmp; cJSON *tree, *j_clients, *j_client, *jtmp, *j_data; int i, count, offset; json_get_bool(command, "verbose", &verbose, true, false); json_get_int(command, "count", &count, true, -1); json_get_int(command, "offset", &offset, true, 0); tree = cJSON_CreateObject(); if(tree == NULL){ dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } jtmp = cJSON_CreateString("listClients"); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(tree, "command", jtmp); j_data = cJSON_CreateObject(); if(j_data == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(tree, "data", j_data); cJSON_AddIntToObject(j_data, "totalCount", (int)HASH_CNT(hh, local_clients)); j_clients = cJSON_CreateArray(); if(j_clients == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(j_data, "clients", j_clients); i = 0; HASH_ITER(hh, local_clients, client, client_tmp){ if(i>=offset){ j_client = add_client_to_json(client, verbose); if(j_client == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_clients, j_client); if(count >= 0){ count--; if(count <= 0){ break; } } } i++; } if(correlation_data){ jtmp = cJSON_CreateString(correlation_data); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data); return 1; } cJSON_AddItemToObject(tree, "correlationData", jtmp); } cJSON_AddItemToArray(j_responses, tree); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *rolename; struct dynsec__client *client; struct dynsec__role *role; int priority; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addClientRole", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addClientRole", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addClientRole", "Invalid/missing rolename", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addClientRole", "Role name not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } json_get_int(command, "priority", &priority, true, -1); client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "addClientRole", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } role = dynsec_roles__find(rolename); if(role == NULL){ dynsec__command_reply(j_responses, context, "addClientRole", "Role not found", correlation_data); return MOSQ_ERR_SUCCESS; } dynsec_rolelists__client_add_role(client, role, priority); dynsec__config_save(); dynsec__command_reply(j_responses, context, "addClientRole", NULL, correlation_data); /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_SUCCESS; } int dynsec_clients__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { char *username, *rolename; struct dynsec__client *client; struct dynsec__role *role; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "removeClientRole", "Invalid/missing username", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(username, (int)strlen(username)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "removeClientRole", "Username not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "removeGroupRole", "Invalid/missing rolename", correlation_data); return MOSQ_ERR_INVAL; } if(mosquitto_validate_utf8(rolename, (int)strlen(rolename)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "removeClientRole", "Role name not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "removeClientRole", "Client not found", correlation_data); return MOSQ_ERR_SUCCESS; } role = dynsec_roles__find(rolename); if(role == NULL){ dynsec__command_reply(j_responses, context, "addClientRole", "Role not found", correlation_data); return MOSQ_ERR_SUCCESS; } dynsec_rolelists__client_remove_role(client, role); dynsec__config_save(); dynsec__command_reply(j_responses, context, "removeClientRole", NULL, correlation_data); /* Enforce any changes */ mosquitto_kick_client_by_username(username, false); return MOSQ_ERR_SUCCESS; }