/* 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 #include #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; 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; /* 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 */ tree = cJSON_ParseWithLength(ed->payload, ed->payloadlen); 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; } 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; 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; } 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; } } } dynsec__config_save(); dynsec__command_reply(j_responses, context, "setDefaultACLAccess", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } 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; tree = cJSON_CreateObject(); if(tree == NULL){ dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } jtmp = cJSON_CreateString("getDefaultACLAccess"); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "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, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(tree, "data", j_data); j_acls = cJSON_AddArrayToObject(j_data, "acls"); if(j_acls == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } /* publishClientSend */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_PUB_C_SEND) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.publish_c_send) == NULL ){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_acls, j_acl); /* publishClientReceive */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_PUB_C_RECV) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.publish_c_recv) == NULL ){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_acls, j_acl); /* subscribe */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_SUB_GENERIC) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.subscribe) == NULL ){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_acls, j_acl); /* unsubscribe */ j_acl = cJSON_CreateObject(); if(j_acl == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } if(cJSON_AddStringToObject(j_acl, "acltype", ACL_TYPE_UNSUB_GENERIC) == NULL || cJSON_AddBoolToObject(j_acl, "allow", default_access.unsubscribe) == NULL ){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_acls, j_acl); cJSON_AddItemToArray(j_responses, tree); if(correlation_data){ jtmp = cJSON_AddStringToObject(tree, "correlationData", correlation_data); if(jtmp == NULL){ cJSON_Delete(tree); dynsec__command_reply(j_responses, context, "getDefaultACLAccess", "Internal error", correlation_data); return 1; } } return MOSQ_ERR_SUCCESS; } int mosquitto_plugin_version(int supported_version_count, const int *supported_versions) { int i; for(i=0; i