From df30b6c9d4b19895f2d007ca96dd79506328a51e Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Thu, 29 Oct 2020 11:38:34 +0000 Subject: [PATCH] Dynsec: add getAnonymousGroup and getDefaultACLAccess commands --- apps/mosquitto_ctrl/dynsec.c | 99 ++++++++++++++++- apps/mosquitto_ctrl/dynsec_group.c | 11 ++ apps/mosquitto_ctrl/mosquitto_ctrl.h | 1 + plugins/dynamic-security/README.md | 45 +++++++- plugins/dynamic-security/dynamic_security.h | 1 + plugins/dynamic-security/groups.c | 47 ++++++++ plugins/dynamic-security/plugin.c | 115 +++++++++++++++++++- 7 files changed, 310 insertions(+), 9 deletions(-) diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index 80bd7b84..6580fb4e 100644 --- a/apps/mosquitto_ctrl/dynsec.c +++ b/apps/mosquitto_ctrl/dynsec.c @@ -31,7 +31,9 @@ void dynsec__print_usage(void) printf(" mosquitto_ctrl dynsec init [admin-role]\n"); printf("\nGeneral\n-------\n"); + printf("Get ACL default access: getDefaultACLAccess\n"); printf("Set ACL default access: setDefaultACLAccess allow|deny\n"); + printf("Get group for anonymous clients: getAnonymousGroup\n"); printf("Set group for anonymous clients: setAnonymousGroup \n"); printf("\nClients\n-------\n"); @@ -82,10 +84,16 @@ static void print_list(cJSON *j_response, const char *arrayname, const char *key cJSON *j_data, *j_array, *j_elem, *j_name; j_data = cJSON_GetObjectItem(j_response, "data"); - if(j_data == NULL) return; + if(j_data == NULL){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } j_array = cJSON_GetObjectItem(j_data, arrayname); - if(j_array == NULL || !cJSON_IsArray(j_array)) return; + if(j_array == NULL || !cJSON_IsArray(j_array)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } cJSON_ArrayForEach(j_elem, j_array){ if(cJSON_IsObject(j_elem)){ @@ -107,16 +115,19 @@ static void print_client(cJSON *j_response) j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_client = cJSON_GetObjectItem(j_data, "client"); if(j_client == NULL || !cJSON_IsObject(j_client)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } jtmp = cJSON_GetObjectItem(j_client, "username"); if(jtmp == NULL || !cJSON_IsString(jtmp)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Username: %s\n", jtmp->valuestring); @@ -186,16 +197,19 @@ static void print_group(cJSON *j_response) j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_group = cJSON_GetObjectItem(j_data, "group"); if(j_group == NULL || !cJSON_IsObject(j_group)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } jtmp = cJSON_GetObjectItem(j_group, "groupname"); if(jtmp == NULL || !cJSON_IsString(jtmp)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Groupname: %s\n", jtmp->valuestring); @@ -248,16 +262,19 @@ static void print_role(cJSON *j_response) j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_role = cJSON_GetObjectItem(j_data, "role"); if(j_role == NULL || !cJSON_IsObject(j_role)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } jtmp = cJSON_GetObjectItem(j_role, "rolename"); if(jtmp == NULL || !cJSON_IsString(jtmp)){ + fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Rolename: %s\n", jtmp->valuestring); @@ -296,6 +313,61 @@ static void print_role(cJSON *j_response) } +static void print_anonymous_group(cJSON *j_response) +{ + cJSON *j_data, *j_group, *j_groupname; + + j_data = cJSON_GetObjectItem(j_response, "data"); + if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + j_group = cJSON_GetObjectItem(j_data, "group"); + if(j_group == NULL || !cJSON_IsObject(j_group)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + j_groupname = cJSON_GetObjectItem(j_group, "groupname"); + if(j_groupname == NULL || !cJSON_IsString(j_groupname)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + printf("%s\n", j_groupname->valuestring); +} + +static void print_default_acl_access(cJSON *j_response) +{ + cJSON *j_data, *j_acls, *j_acl, *j_acltype, *j_allow; + + j_data = cJSON_GetObjectItem(j_response, "data"); + if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + j_acls = cJSON_GetObjectItem(j_data, "acls"); + if(j_acls == NULL || !cJSON_IsArray(j_acls)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + cJSON_ArrayForEach(j_acl, j_acls){ + j_acltype = cJSON_GetObjectItem(j_acl, "acltype"); + j_allow = cJSON_GetObjectItem(j_acl, "allow"); + + if(j_acltype == NULL || !cJSON_IsString(j_acltype) + || j_allow == NULL || !cJSON_IsBool(j_allow) + ){ + + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + printf("%-20s : %s\n", j_acltype->valuestring, cJSON_IsTrue(j_allow)?"allow":"deny"); + } +} + static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload) { cJSON *tree, *j_responses, *j_response, *j_command, *j_error; @@ -343,6 +415,10 @@ static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, co print_group(j_response); }else if(!strcasecmp(j_command->valuestring, "getRole")){ print_role(j_response); + }else if(!strcasecmp(j_command->valuestring, "getDefaultACLAccess")){ + print_default_acl_access(j_response); + }else if(!strcasecmp(j_command->valuestring, "getAnonymousGroup")){ + print_anonymous_group(j_response); }else{ //fprintf(stderr, "%s: Success\n", j_command->valuestring); } @@ -356,7 +432,7 @@ static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, co * # * ################################################################ */ -static int dynsec__default_acl_access(int argc, char *argv[], cJSON *j_command) +static int dynsec__set_default_acl_access(int argc, char *argv[], cJSON *j_command) { char *acltype, *access; cJSON *j_acls, *j_acl; @@ -403,6 +479,17 @@ static int dynsec__default_acl_access(int argc, char *argv[], cJSON *j_command) return MOSQ_ERR_SUCCESS; } +static int dynsec__get_default_acl_access(int argc, char *argv[], cJSON *j_command) +{ + if(cJSON_AddStringToObject(j_command, "command", "getDefaultACLAccess") == NULL + ){ + + return MOSQ_ERR_NOMEM; + } + + return MOSQ_ERR_SUCCESS; +} + /* ################################################################ * # * # Init @@ -673,7 +760,9 @@ int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl) cJSON_AddItemToArray(j_commands, j_command); if(!strcasecmp(argv[0], "setDefaultACLAccess")){ - return dynsec__default_acl_access(argc-1, &argv[1], j_command); + return dynsec__set_default_acl_access(argc-1, &argv[1], j_command); + }else if(!strcasecmp(argv[0], "getDefaultACLAccess")){ + return dynsec__get_default_acl_access(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "createClient")){ return dynsec_client__create(argc-1, &argv[1], j_command); @@ -708,6 +797,8 @@ int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl) return dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "setAnonymousGroup")){ return dynsec_group__set_anonymous(argc-1, &argv[1], j_command); + }else if(!strcasecmp(argv[0], "getAnonymousGroup")){ + return dynsec_group__get_anonymous(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "createRole")){ return dynsec_role__create(argc-1, &argv[1], j_command); diff --git a/apps/mosquitto_ctrl/dynsec_group.c b/apps/mosquitto_ctrl/dynsec_group.c index 720e80f9..cf3a6ce0 100644 --- a/apps/mosquitto_ctrl/dynsec_group.c +++ b/apps/mosquitto_ctrl/dynsec_group.c @@ -62,6 +62,17 @@ int dynsec_group__delete(int argc, char *argv[], cJSON *j_command) } } +int dynsec_group__get_anonymous(int argc, char *argv[], cJSON *j_command) +{ + if(cJSON_AddStringToObject(j_command, "command", "getAnonymousGroup") == NULL + ){ + + return MOSQ_ERR_NOMEM; + }else{ + return MOSQ_ERR_SUCCESS; + } +} + int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command) { char *groupname = NULL; diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.h b/apps/mosquitto_ctrl/mosquitto_ctrl.h index fc7220b7..d9a10cb8 100644 --- a/apps/mosquitto_ctrl/mosquitto_ctrl.h +++ b/apps/mosquitto_ctrl/mosquitto_ctrl.h @@ -98,6 +98,7 @@ int dynsec_group__delete(int argc, char *argv[], cJSON *j_command); int dynsec_group__get(int argc, char *argv[], cJSON *j_command); int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command); int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command); +int dynsec_group__get_anonymous(int argc, char *argv[], cJSON *j_command); int dynsec_role__create(int argc, char *argv[], cJSON *j_command); int dynsec_role__delete(int argc, char *argv[], cJSON *j_command); diff --git a/plugins/dynamic-security/README.md b/plugins/dynamic-security/README.md index d2054f85..e72c118f 100644 --- a/plugins/dynamic-security/README.md +++ b/plugins/dynamic-security/README.md @@ -27,7 +27,8 @@ subscribe to. Sets the default access behaviour for the different ACL types, assuming there are no matching ACLs for a topic. -By default, all ACL types default to deny. +By default, publishClientSend and subscribe default to deny, and +publishClientReceive and unsubscribe default to allow. Command: ``` @@ -37,9 +38,9 @@ Command: "command": "setDefaultACLAccess", "acls":[ { "acltype": "publishClientSend", "allow": false }, - { "acltype": "publishClientReceive", "allow": false }, + { "acltype": "publishClientReceive", "allow": true }, { "acltype": "subscribe", "allow": false }, - { "acltype": "unsubscribe", "allow": false } + { "acltype": "unsubscribe", "allow": true } ] } ] @@ -51,6 +52,26 @@ mosquitto_ctrl example: mosquitto_ctrl dynsec setDefaultACLAccess subscribe deny ``` +### Get default ACL access + +Gets the default access behaviour for the different ACL types. + +Command: +``` +{ + "commands":[ + { + "command": "getDefaultACLAccess", + } + ] +} +``` + +mosquitto_ctrl example: +``` +mosquitto_ctrl dynsec getDefaultACLAccess +``` + ## Create Client Command: @@ -432,6 +453,24 @@ mosquitto_ctrl example: mosquitto_ctrl dynsec setAnonymousGroup groupname ``` +## Get Group for Anonymous Clients + +Command: +``` +{ + "commands":[ + { + "command": "getAnonymousGroup", + } + ] +} +``` + +mosquitto_ctrl example: +``` +mosquitto_ctrl dynsec getAnonymousGroup +``` + ## Create Role Command: diff --git a/plugins/dynamic-security/dynamic_security.h b/plugins/dynamic-security/dynamic_security.h index 9f124d4c..8a1c0cd6 100644 --- a/plugins/dynamic-security/dynamic_security.h +++ b/plugins/dynamic-security/dynamic_security.h @@ -204,6 +204,7 @@ int dynsec_groups__process_list(cJSON *j_responses, struct mosquitto *context, c int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); int dynsec_groups__process_remove_client(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); +int dynsec_groups__process_get_anonymous_group(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data); 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); diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index f1daccaa..6fc997dc 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -1021,3 +1021,50 @@ int dynsec_groups__process_set_anonymous_group(cJSON *j_responses, struct mosqui dynsec__command_reply(j_responses, context, "setAnonymousGroup", NULL, correlation_data); return MOSQ_ERR_SUCCESS; } + +int dynsec_groups__process_get_anonymous_group(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) +{ + cJSON *tree, *jtmp, *j_data, *j_group; + + tree = cJSON_CreateObject(); + if(tree == NULL){ + dynsec__command_reply(j_responses, context, "getAnonymousGroup", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; + } + + jtmp = cJSON_CreateString("getAnonymousGroup"); + if(jtmp == NULL){ + cJSON_Delete(tree); + dynsec__command_reply(j_responses, context, "getAnonymousGroup", "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, "getAnonymousGroup", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(tree, "data", j_data); + + j_group = cJSON_CreateObject(); + if(j_group == NULL){ + cJSON_Delete(tree); + dynsec__command_reply(j_responses, context, "getAnonymousGroup", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(j_data, "group", j_group); + + if(cJSON_AddStringToObject(j_group, "groupname", dynsec_anonymous_group->groupname) == NULL + ){ + + cJSON_Delete(tree); + dynsec__command_reply(j_responses, context, "getAnonymousGroup", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; + } + + cJSON_AddItemToArray(j_responses, tree); + + return MOSQ_ERR_SUCCESS; +} diff --git a/plugins/dynamic-security/plugin.c b/plugins/dynamic-security/plugin.c index 9b0aecec..5df2a0e9 100644 --- a/plugins/dynamic-security/plugin.c +++ b/plugins/dynamic-security/plugin.c @@ -109,7 +109,7 @@ static int dynsec_control_callback(int event, void *event_data, void *userdata) return MOSQ_ERR_SUCCESS; } -int dynsec__process_default_acl_access(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) +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; @@ -146,6 +146,113 @@ int dynsec__process_default_acl_access(cJSON *j_responses, struct mosquitto *con } +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); + + return MOSQ_ERR_SUCCESS; +} + + int mosquitto_plugin_version(int supported_version_count, const int *supported_versions) { int i; @@ -408,7 +515,9 @@ int dynsec__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON /* Plugin */ if(!strcasecmp(command, "setDefaultACLAccess")){ - rc = dynsec__process_default_acl_access(j_responses, context, aiter, correlation_data); + rc = dynsec__process_set_default_acl_access(j_responses, context, aiter, correlation_data); + }else if(!strcasecmp(command, "getDefaultACLAccess")){ + rc = dynsec__process_get_default_acl_access(j_responses, context, aiter, correlation_data); /* Clients */ }else if(!strcasecmp(command, "createClient")){ @@ -449,6 +558,8 @@ int dynsec__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON rc = dynsec_groups__process_remove_role(j_responses, context, aiter, correlation_data); }else if(!strcasecmp(command, "setAnonymousGroup")){ rc = dynsec_groups__process_set_anonymous_group(j_responses, context, aiter, correlation_data); + }else if(!strcasecmp(command, "getAnonymousGroup")){ + rc = dynsec_groups__process_get_anonymous_group(j_responses, context, aiter, correlation_data); /* Roles */ }else if(!strcasecmp(command, "createRole")){