mosquitto/plugins/dynamic-security/clients.c

1090 lines
34 KiB
C
Raw Normal View History

2020-09-23 21:59:31 +00:00
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
2020-11-25 17:34:21 +00:00
are made available under the terms of the Eclipse Public License 2.0
2020-09-23 21:59:31 +00:00
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
2020-11-25 17:34:21 +00:00
https://www.eclipse.org/legal/epl-2.0/
2020-09-23 21:59:31 +00:00
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 <cJSON.h>
#include <stdio.h>
#include <uthash.h>
#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
* #
* ################################################################ */
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_rolelist__cleanup(&client->rolelist);
2020-09-23 21:59:31 +00:00
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;
2020-09-23 21:59:31 +00:00
struct dynsec__client *client;
struct dynsec__role *role;
unsigned char *buf;
2020-11-03 10:09:18 +00:00
int buf_len;
2020-09-23 21:59:31 +00:00
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);
}
2020-09-23 21:59:31 +00:00
/* Salt */
j_salt = cJSON_GetObjectItem(j_client, "salt");
j_password = cJSON_GetObjectItem(j_client, "password");
j_iterations = cJSON_GetObjectItem(j_client, "iterations");
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
if(dynsec_auth__base64_decode(j_salt->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS
|| buf_len != sizeof(client->pw.salt)){
2020-09-23 21:59:31 +00:00
// FIXME log
mosquitto_free(client->username);
mosquitto_free(client);
continue;
}
2020-11-03 10:09:18 +00:00
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;
}
2020-11-03 10:09:18 +00:00
memcpy(client->pw.password_hash, buf, (size_t)buf_len);
mosquitto_free(buf);
client->pw.valid = true;
}else{
client->pw.valid = false;
2020-09-23 21:59:31 +00:00
}
/* 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_rolelist__client_add(client, role, priority);
2020-09-23 21:59:31 +00:00
}
}
}
}
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)
2020-09-23 21:59:31 +00:00
){
return 1;
}
j_roles = dynsec_rolelist__all_to_json(client->rolelist);
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
if(dynsec_auth__base64_encode(client->pw.salt, sizeof(client->pw.salt), &buf) != MOSQ_ERR_SUCCESS){
return 1;
}
2020-09-23 21:59:31 +00:00
jtmp = cJSON_CreateString(buf);
mosquitto_free(buf);
if(jtmp == NULL) return 1;
cJSON_AddItemToObject(j_client, "salt", jtmp);
2020-09-23 21:59:31 +00:00
2020-10-29 23:51:32 +00:00
if(cJSON_AddIntToObject(j_client, "iterations", client->pw.iterations) == NULL){
return 1;
}
2020-09-23 21:59:31 +00:00
}
}
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;
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
if(json_get_string(command, "password", &password, true) != MOSQ_ERR_SUCCESS){
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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;
2020-09-23 21:59:31 +00:00
}
2020-11-18 13:08:09 +00:00
if(clientid && strlen(clientid) > 0){
2020-09-23 21:59:31 +00:00
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_rolelist__load_from_json(command, &client->rolelist);
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
2020-11-17 14:58:23 +00:00
int dynsec_clients__process_set_id(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
{
char *username, *clientid, *clientid_heap = NULL;
2020-11-17 14:58:23 +00:00
struct dynsec__client *client;
size_t slen;
2020-11-17 14:58:23 +00:00
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, true) != MOSQ_ERR_SUCCESS){
2020-11-17 14:58:23 +00:00
dynsec__command_reply(j_responses, context, "setClientId", "Invalid/missing client ID", correlation_data);
return MOSQ_ERR_INVAL;
}
if(clientid){
slen = strlen(clientid);
if(mosquitto_validate_utf8(clientid, (int)slen) != MOSQ_ERR_SUCCESS){
dynsec__command_reply(j_responses, context, "setClientId", "Client ID not valid UTF-8", correlation_data);
return MOSQ_ERR_INVAL;
}
if(slen > 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;
}
2020-11-17 14:58:23 +00:00
}
client = dynsec_clients__find(username);
if(client == NULL){
mosquitto_free(clientid_heap);
2020-11-17 14:58:23 +00:00
dynsec__command_reply(j_responses, context, "setClientId", "Client not found", correlation_data);
return MOSQ_ERR_SUCCESS;
}
mosquitto_free(client->clientid);
2020-11-17 14:58:23 +00:00
client->clientid = clientid_heap;
dynsec__config_save();
dynsec__command_reply(j_responses, context, "setClientId", NULL, correlation_data);
2020-11-17 14:58:23 +00:00
/* 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;
}
}
2020-09-23 21:59:31 +00:00
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;
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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;
}
if(strlen(password) == 0){
dynsec__command_reply(j_responses, context, "setClientPassword", "Empty password is not allowed", correlation_data);
return MOSQ_ERR_INVAL;
}
2020-09-23 21:59:31 +00:00
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){
2020-09-23 21:59:31 +00:00
dynsec__config_save();
dynsec__command_reply(j_responses, context, "setClientPassword", NULL, correlation_data);
/* Enforce any changes */
mosquitto_kick_client_by_username(username, false);
2020-09-23 21:59:31 +00:00
}else{
dynsec__command_reply(j_responses, context, "setClientPassword", "Internal error", correlation_data);
}
return rc;
2020-09-23 21:59:31 +00:00
}
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_rolelist__client_add(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_rolelist__client_remove(client, rolelist->role);
}
}
2020-09-23 21:59:31 +00:00
int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
{
char *username;
char *clientid;
char *password;
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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){
if(clientid && strlen(clientid) > 0){
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);
}else{
str = NULL;
}
client->clientid = str;
}
if(json_get_string(command, "password", &password, false) == MOSQ_ERR_SUCCESS){
if(strlen(password) > 0){
/* If password == "", we just ignore it */
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){
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
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){
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
return MOSQ_ERR_NOMEM;
}
mosquitto_free(client->text_description);
client->text_description = str;
}
rc = dynsec_rolelist__load_from_json(command, &rolelist);
2020-09-23 21:59:31 +00:00
if(rc == MOSQ_ERR_SUCCESS){
client__remove_all_roles(client);
client__add_new_roles(client, rolelist);
dynsec_rolelist__cleanup(&rolelist);
2020-09-23 21:59:31 +00:00
}else if(rc == MOSQ_ERR_NOT_FOUND){
dynsec__command_reply(j_responses, context, "modifyClient", "Role not found", correlation_data);
dynsec_rolelist__cleanup(&rolelist);
mosquitto_kick_client_by_username(username, false);
2020-09-23 21:59:31 +00:00
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_rolelist__cleanup(&rolelist);
mosquitto_kick_client_by_username(username, false);
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
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){
2020-11-18 11:45:31 +00:00
dynsec_groups__remove_client(username, grouplist->group->groupname, false);
2020-09-23 21:59:31 +00:00
}
}
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)
2020-09-23 21:59:31 +00:00
){
cJSON_Delete(j_client);
return NULL;
}
j_roles = dynsec_rolelist__all_to_json(client->rolelist);
2020-09-23 21:59:31 +00:00
if(j_roles == NULL){
cJSON_Delete(j_client);
return NULL;
}
cJSON_AddItemToObject(j_client, "roles", j_roles);
2020-11-18 11:45:31 +00:00
j_groups = dynsec_grouplist__all_to_json(client->grouplist);
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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_AddStringToObject(tree, "correlationData", correlation_data);
2020-09-23 21:59:31 +00:00
if(jtmp == NULL){
cJSON_Delete(tree);
dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data);
return 1;
}
}
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);
2020-11-03 10:09:18 +00:00
cJSON_AddIntToObject(j_data, "totalCount", (int)HASH_CNT(hh, local_clients));
2020-09-23 21:59:31 +00:00
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_AddStringToObject(tree, "correlationData", correlation_data);
2020-09-23 21:59:31 +00:00
if(jtmp == NULL){
cJSON_Delete(tree);
dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data);
return 1;
}
}
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;
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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);
2020-09-23 21:59:31 +00:00
if(role == NULL){
dynsec__command_reply(j_responses, context, "addClientRole", "Role not found", correlation_data);
return MOSQ_ERR_SUCCESS;
}
dynsec_rolelist__client_add(client, role, priority);
2020-09-23 21:59:31 +00:00
dynsec__config_save();
dynsec__command_reply(j_responses, context, "addClientRole", NULL, correlation_data);
/* Enforce any changes */
mosquitto_kick_client_by_username(username, false);
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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;
}
2020-09-23 21:59:31 +00:00
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_rolelist__client_remove(client, role);
2020-09-23 21:59:31 +00:00
dynsec__config_save();
dynsec__command_reply(j_responses, context, "removeClientRole", NULL, correlation_data);
/* Enforce any changes */
mosquitto_kick_client_by_username(username, false);
2020-09-23 21:59:31 +00:00
return MOSQ_ERR_SUCCESS;
}