/* 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 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 #include #include #include #ifndef WIN32 # include #endif #include "json_help.h" #include "mosquitto.h" #include "mosquitto_broker.h" #include "mosquitto_plugin.h" #include "mqtt_protocol.h" #include "dynamic_security.h" static mosquitto_plugin_id_t *plg_id = NULL; static char *config_file = NULL; struct dynsec__acl_default_access default_access = {false, false, false, false}; void dynsec__command_reply(cJSON *j_responses, struct mosquitto *context, const char *command, const char *error, const char *correlation_data) { cJSON *j_response; UNUSED(context); j_response = cJSON_CreateObject(); if(j_response == NULL) return; if(cJSON_AddStringToObject(j_response, "command", command) == NULL || (error && cJSON_AddStringToObject(j_response, "error", error) == NULL) || (correlation_data && cJSON_AddStringToObject(j_response, "correlationData", correlation_data) == NULL) ){ cJSON_Delete(j_response); return; } cJSON_AddItemToArray(j_responses, j_response); } static void send_response(cJSON *tree) { char *payload; size_t payload_len; payload = cJSON_PrintUnformatted(tree); cJSON_Delete(tree); if(payload == NULL) return; payload_len = strlen(payload); if(payload_len > MQTT_MAX_PAYLOAD){ free(payload); return; } mosquitto_broker_publish(NULL, "$CONTROL/dynamic-security/v1/response", (int)payload_len, payload, 0, 0, NULL); } static int dynsec_control_callback(int event, void *event_data, void *userdata) { struct mosquitto_evt_control *ed = event_data; cJSON *tree, *commands; cJSON *j_response_tree, *j_responses; UNUSED(event); UNUSED(userdata); /* Create object for responses */ j_response_tree = cJSON_CreateObject(); if(j_response_tree == NULL){ return MOSQ_ERR_NOMEM; } j_responses = cJSON_CreateArray(); if(j_responses == NULL){ cJSON_Delete(j_response_tree); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(j_response_tree, "responses", j_responses); /* Parse cJSON tree. * Using cJSON_ParseWithLength() is the best choice here, but Mosquitto * always adds an extra 0 to the end of the payload memory, so using * cJSON_Parse() on its own will still not overrun. */ #if CJSON_VERSION_FULL < 1007013 tree = cJSON_Parse(ed->payload); #else tree = cJSON_ParseWithLength(ed->payload, ed->payloadlen); #endif if(tree == NULL){ dynsec__command_reply(j_responses, ed->client, "Unknown command", "Payload not valid JSON", NULL); send_response(j_response_tree); return MOSQ_ERR_SUCCESS; } commands = cJSON_GetObjectItem(tree, "commands"); if(commands == NULL || !cJSON_IsArray(commands)){ cJSON_Delete(tree); dynsec__command_reply(j_responses, ed->client, "Unknown command", "Invalid/missing commands", NULL); send_response(j_response_tree); return MOSQ_ERR_SUCCESS; } /* Handle commands */ dynsec__handle_control(j_responses, ed->client, commands); cJSON_Delete(tree); send_response(j_response_tree); return MOSQ_ERR_SUCCESS; } static int dynsec__process_set_default_acl_access(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { cJSON *j_actions, *j_action, *j_acltype, *j_allow; bool allow; const char *admin_clientid, *admin_username; j_actions = cJSON_GetObjectItem(command, "acls"); if(j_actions == NULL || !cJSON_IsArray(j_actions)){ dynsec__command_reply(j_responses, context, "setDefaultACLAccess", "Missing/invalid actions array", correlation_data); return MOSQ_ERR_INVAL; } admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); cJSON_ArrayForEach(j_action, j_actions){ j_acltype = cJSON_GetObjectItem(j_action, "acltype"); j_allow = cJSON_GetObjectItem(j_action, "allow"); if(j_acltype && cJSON_IsString(j_acltype) && j_allow && cJSON_IsBool(j_allow)){ allow = cJSON_IsTrue(j_allow); if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_SEND)){ default_access.publish_c_send = allow; }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_RECV)){ default_access.publish_c_recv = allow; }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_GENERIC)){ default_access.subscribe = allow; }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_GENERIC)){ default_access.unsubscribe = allow; } mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | setDefaultACLAccess | acltype=%s | allow=%s", admin_clientid, admin_username, j_acltype->valuestring, allow?"true":"false"); } } dynsec__config_save(); dynsec__command_reply(j_responses, context, "setDefaultACLAccess", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } static int dynsec__process_get_default_acl_access(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) { cJSON *tree, *jtmp, *j_data, *j_acls, *j_acl; const char *admin_clientid, *admin_username; UNUSED(command); tree = cJSON_CreateObject(); if(tree == NULL){ dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | getDefaultACLAccess", admin_clientid, admin_username); if(cJSON_AddStringToObject(tree, "command", "getDefaultACLAccess") == NULL || ((j_data = cJSON_AddObjectToObject(tree, "data")) == NULL) ){ goto internal_error; } j_acls = cJSON_AddArrayToObject(j_data, "acls"); if(j_acls == NULL){ goto internal_error; } /* publishClientSend */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ goto internal_error; } cJSON_AddItemToArray(j_acls, j_acl); if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_PUB_C_SEND) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.publish_c_send) == NULL ){ goto internal_error; } /* publishClientReceive */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ goto internal_error; } cJSON_AddItemToArray(j_acls, j_acl); if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_PUB_C_RECV) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.publish_c_recv) == NULL ){ goto internal_error; } /* subscribe */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ goto internal_error; } cJSON_AddItemToArray(j_acls, j_acl); if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_SUB_GENERIC) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.subscribe) == NULL ){ goto internal_error; } /* unsubscribe */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ goto internal_error; } cJSON_AddItemToArray(j_acls, j_acl); if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_UNSUB_GENERIC) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.unsubscribe) == NULL ){ goto internal_error; } cJSON_AddItemToArray(j_responses, tree); if(correlation_data){ jtmp = cJSON_AddStringToObject(tree, "correlationData", correlation_data); if(jtmp == NULL){ goto internal_error; } } return MOSQ_ERR_SUCCESS; internal_error: cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } int mosquitto_plugin_version(int supported_version_count, const int *supported_versions) { int i; for(i=0; i