From a25fc166a89aeeeb795a3bf8a0ce77918fcc385a Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Tue, 7 Sep 2021 23:17:31 +0100 Subject: [PATCH] Add $CONTROL/broker/v1 control & the `getPluginInfo` command. --- ChangeLog.txt | 3 + apps/mosquitto_ctrl/CMakeLists.txt | 1 + apps/mosquitto_ctrl/Makefile | 4 + apps/mosquitto_ctrl/broker.c | 213 +++++++++++++++++++ apps/mosquitto_ctrl/mosquitto_ctrl.c | 6 +- apps/mosquitto_ctrl/mosquitto_ctrl.h | 3 + config.mk | 3 + plugins/dynamic-security/config_init.c | 33 ++- src/CMakeLists.txt | 2 + src/Makefile | 8 + src/broker_control.c | 284 +++++++++++++++++++++++++ src/json_help.c | 106 +++++++++ src/json_help.h | 34 +++ src/mosquitto.c | 8 + src/mosquitto_broker_internal.h | 5 + 15 files changed, 710 insertions(+), 3 deletions(-) create mode 100644 apps/mosquitto_ctrl/broker.c create mode 100644 src/broker_control.c create mode 100644 src/json_help.c create mode 100644 src/json_help.h diff --git a/ChangeLog.txt b/ChangeLog.txt index a99200ac0..59906ea93 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -51,6 +51,9 @@ Broker: having to connect to a broker. - Add `mosquitto_plugin_set_info()` to allow plugins to tell the broker their name and version. +- Add builtin $CONTROL/broker/v1 control topic with the `getPluginInfo` + command. +- Add support for `getPluginInfo` to mosquitto_ctrl. Client library: - Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair diff --git a/apps/mosquitto_ctrl/CMakeLists.txt b/apps/mosquitto_ctrl/CMakeLists.txt index 35c7837e7..c0c16298b 100644 --- a/apps/mosquitto_ctrl/CMakeLists.txt +++ b/apps/mosquitto_ctrl/CMakeLists.txt @@ -5,6 +5,7 @@ if(WITH_TLS AND CJSON_FOUND) add_executable(mosquitto_ctrl mosquitto_ctrl.c mosquitto_ctrl.h + broker.c client.c dynsec.c ../../plugins/dynamic-security/hash.c diff --git a/apps/mosquitto_ctrl/Makefile b/apps/mosquitto_ctrl/Makefile index 2afd4dbb7..5ed418b27 100644 --- a/apps/mosquitto_ctrl/Makefile +++ b/apps/mosquitto_ctrl/Makefile @@ -15,6 +15,7 @@ endif LOCAL_CPPFLAGS:=-I../mosquitto_passwd -I../../plugins/dynamic-security -DWITH_CJSON OBJS= mosquitto_ctrl.o \ + broker.o \ client.o \ dynsec.o \ dynsec_client.o \ @@ -51,6 +52,9 @@ mosquitto_ctrl_example.so : ${EXAMPLE_OBJS} mosquitto_ctrl.o : mosquitto_ctrl.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@ +broker.o : broker.c mosquitto_ctrl.h + ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@ + client.o : client.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@ diff --git a/apps/mosquitto_ctrl/broker.c b/apps/mosquitto_ctrl/broker.c new file mode 100644 index 000000000..881dbef16 --- /dev/null +++ b/apps/mosquitto_ctrl/broker.c @@ -0,0 +1,213 @@ +/* +Copyright (c) 2021 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 "mosquitto_ctrl.h" +#include "mosquitto.h" + +void broker__print_usage(void) +{ + printf("\nBroker Control module\n"); + printf("=======================\n"); + + printf("Get plugin information: getPluginInfo\n"); +} + +/* ################################################################ + * # + * # Payload callback + * # + * ################################################################ */ + +static void print_plugin_info(cJSON *j_response) +{ + cJSON *j_data, *j_plugins, *j_plugin, *jtmp, *j_eps; + bool first; + + 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_plugins = cJSON_GetObjectItem(j_data, "plugins"); + if(j_plugins == NULL || !cJSON_IsArray(j_plugins)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + cJSON_ArrayForEach(j_plugin, j_plugins){ + jtmp = cJSON_GetObjectItem(j_plugin, "name"); + if(jtmp == NULL || !cJSON_IsString(jtmp)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + printf("Plugin: %-20s\n", jtmp->valuestring); + + jtmp = cJSON_GetObjectItem(j_plugin, "version"); + if(jtmp && cJSON_IsString(jtmp)){ + printf("Version: %-20s\n", jtmp->valuestring); + } + + j_eps = cJSON_GetObjectItem(j_plugin, "control-endpoints"); + if(j_eps && cJSON_IsArray(j_eps)){ + first = true; + cJSON_ArrayForEach(jtmp, j_eps){ + if(jtmp && cJSON_IsString(jtmp)){ + if(first){ + first = false; + printf("Control endpoints: %-20s\n", jtmp->valuestring); + }else{ + printf(" %-20s\n", jtmp->valuestring); + } + } + } + } + } +} + + +static void broker__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload) +{ + cJSON *tree, *j_responses, *j_response, *j_command, *j_error; + + UNUSED(ctrl); + +#if CJSON_VERSION_FULL < 1007013 + UNUSED(payloadlen); + tree = cJSON_Parse(payload); +#else + tree = cJSON_ParseWithLength(payload, (size_t)payloadlen); +#endif + if(tree == NULL){ + fprintf(stderr, "Error: Payload not JSON.\n"); + return; + } + + j_responses = cJSON_GetObjectItem(tree, "responses"); + if(j_responses == NULL || !cJSON_IsArray(j_responses)){ + fprintf(stderr, "Error: Payload missing data.\n"); + cJSON_Delete(tree); + return; + } + + j_response = cJSON_GetArrayItem(j_responses, 0); + if(j_response == NULL){ + fprintf(stderr, "Error: Payload missing data.\n"); + cJSON_Delete(tree); + return; + } + + j_command = cJSON_GetObjectItem(j_response, "command"); + if(j_command == NULL){ + fprintf(stderr, "Error: Payload missing data.\n"); + cJSON_Delete(tree); + return; + } + + j_error = cJSON_GetObjectItem(j_response, "error"); + if(j_error){ + fprintf(stderr, "%s: Error: %s\n", j_command->valuestring, j_error->valuestring); + }else{ + if(!strcasecmp(j_command->valuestring, "getPluginInfo")){ + print_plugin_info(j_response); + }else{ + /* fprintf(stderr, "%s: Success\n", j_command->valuestring); */ + } + } + cJSON_Delete(tree); +} + +static int broker__get_plugin_info(int argc, char *argv[], cJSON *j_command) +{ + UNUSED(argc); + UNUSED(argv); + + if(cJSON_AddStringToObject(j_command, "command", "getPluginInfo") == NULL + ){ + + return MOSQ_ERR_NOMEM; + } + + return MOSQ_ERR_SUCCESS; +} + +/* ################################################################ + * # + * # Main + * # + * ################################################################ */ + +int broker__main(int argc, char *argv[], struct mosq_ctrl *ctrl) +{ + int rc = -1; + cJSON *j_tree; + cJSON *j_commands, *j_command; + + if(!strcasecmp(argv[0], "help")){ + broker__print_usage(); + return -1; + } + + /* The remaining commands need a network connection and JSON command. */ + + ctrl->payload_callback = broker__payload_callback; + ctrl->request_topic = strdup("$CONTROL/broker/v1"); + ctrl->response_topic = strdup("$CONTROL/broker/v1/response"); + if(ctrl->request_topic == NULL || ctrl->response_topic == NULL){ + return MOSQ_ERR_NOMEM; + } + j_tree = cJSON_CreateObject(); + if(j_tree == NULL) return MOSQ_ERR_NOMEM; + j_commands = cJSON_AddArrayToObject(j_tree, "commands"); + if(j_commands == NULL){ + cJSON_Delete(j_tree); + j_tree = NULL; + return MOSQ_ERR_NOMEM; + } + j_command = cJSON_CreateObject(); + if(j_command == NULL){ + cJSON_Delete(j_tree); + j_tree = NULL; + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToArray(j_commands, j_command); + + if(!strcasecmp(argv[0], "getPluginInfo")){ + rc = broker__get_plugin_info(argc-1, &argv[1], j_command); + + }else{ + fprintf(stderr, "Command '%s' not recognised.\n", argv[0]); + return MOSQ_ERR_UNKNOWN; + } + + if(rc == MOSQ_ERR_SUCCESS){ + ctrl->payload = cJSON_PrintUnformatted(j_tree); + cJSON_Delete(j_tree); + if(ctrl->payload == NULL){ + fprintf(stderr, "Error: Out of memory.\n"); + return MOSQ_ERR_NOMEM; + } + } + return rc; +} diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.c b/apps/mosquitto_ctrl/mosquitto_ctrl.c index cded21000..52a329c27 100644 --- a/apps/mosquitto_ctrl/mosquitto_ctrl.c +++ b/apps/mosquitto_ctrl/mosquitto_ctrl.c @@ -42,7 +42,7 @@ static void print_usage(void) print_version(); printf("\nGeneral usage: mosquitto_ctrl \n"); printf("For module specific help use: mosquitto_ctrl help\n"); - printf("\nModules available: dynsec\n"); + printf("\nModules available: broker dynsec\n"); printf("\nFor more information see:\n"); printf(" https://mosquitto.org/man/mosquitto_ctrl-1.html\n\n"); } @@ -76,7 +76,9 @@ int main(int argc, char *argv[]) } /* In built modules */ - if(!strcasecmp(argv[0], "dynsec")){ + if(!strcasecmp(argv[0], "broker")){ + l_ctrl_main = broker__main; + }else if(!strcasecmp(argv[0], "dynsec")){ l_ctrl_main = dynsec__main; }else{ /* Attempt external module */ diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.h b/apps/mosquitto_ctrl/mosquitto_ctrl.h index 0dd86cb9d..49ab17204 100644 --- a/apps/mosquitto_ctrl/mosquitto_ctrl.h +++ b/apps/mosquitto_ctrl/mosquitto_ctrl.h @@ -89,6 +89,9 @@ int client_connect(struct mosquitto *mosq, struct mosq_config *cfg); cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, int number); +void broker__print_usage(void); +int broker__main(int argc, char *argv[], struct mosq_ctrl *ctrl); + void dynsec__print_usage(void); int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl); diff --git a/config.mk b/config.mk index 1288aa30a..0c4f15bbb 100644 --- a/config.mk +++ b/config.mk @@ -108,6 +108,7 @@ WITH_COVERAGE:=no WITH_UNIX_SOCKETS:=yes # Build mosquitto_sub with cJSON support +# Build mosquitto with broker control support WITH_CJSON:=yes # Build mosquitto with support for the $CONTROL topics. @@ -378,6 +379,8 @@ ifeq ($(WITH_CJSON),yes) CLIENT_LDADD:=$(CLIENT_LDADD) -lcjson CLIENT_STATIC_LDADD:=$(CLIENT_STATIC_LDADD) -lcjson CLIENT_LDFLAGS:=$(CLIENT_LDFLAGS) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_CJSON + BROKER_LDADD:=$(BROKER_LDADD) -lcjson endif ifeq ($(WITH_XTREPORT),yes) diff --git a/plugins/dynamic-security/config_init.c b/plugins/dynamic-security/config_init.c index 86ec3c5c0..1e3dfac6e 100644 --- a/plugins/dynamic-security/config_init.c +++ b/plugins/dynamic-security/config_init.c @@ -160,7 +160,8 @@ static int client_add_admin(FILE *pwfile, cJSON *j_clients) free(password_hash); free(salt); - if(client_role_add(j_roles, "dynsec-admin") + if(client_role_add(j_roles, "broker-admin") + || client_role_add(j_roles, "dynsec-admin") || client_role_add(j_roles, "sys-observe") || client_role_add(j_roles, "topic-observe") ){ @@ -319,6 +320,35 @@ static int role_add_client(cJSON *j_roles) return MOSQ_ERR_SUCCESS; } +static int role_add_broker_admin(cJSON *j_roles) +{ + cJSON *j_role, *j_acls; + + j_role = cJSON_CreateObject(); + if(j_role == NULL){ + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToArray(j_roles, j_role); + + if(cJSON_AddStringToObject(j_role, "rolename", "broker-admin") == NULL + || cJSON_AddStringToObject(j_role, "textdescription", + "Grants access to administer general broker configuration.") == NULL + || (j_acls = cJSON_AddArrayToObject(j_role, "acls")) == NULL + ){ + + return MOSQ_ERR_NOMEM; + } + + if(acl_add(j_acls, "publishClientSend", "$CONTROL/broker/#", 0, true) + || acl_add(j_acls, "publishClientReceive", "$CONTROL/broker/#", 0, true) + || acl_add(j_acls, "subscribePattern", "$CONTROL/broker/#", 0, true) + ){ + + return MOSQ_ERR_NOMEM; + } + return MOSQ_ERR_SUCCESS; +} + static int role_add_dynsec_admin(cJSON *j_roles) { cJSON *j_role, *j_acls; @@ -443,6 +473,7 @@ static int add_roles(cJSON *j_tree) } if(role_add_client(j_roles) + || role_add_broker_admin(j_roles) || role_add_dynsec_admin(j_roles) || role_add_sys_notify(j_roles) || role_add_sys_observe(j_roles) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5313a758..f2c4fb246 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ set (MOSQ_SRCS ../lib/alias_mosq.c ../lib/alias_mosq.h bridge.c bridge_topic.c + broker_control.c conf.c conf_includedir.c context.c @@ -19,6 +20,7 @@ set (MOSQ_SRCS handle_subscribe.c ../lib/handle_unsuback.c handle_unsubscribe.c + json_help.c json_help.h keepalive.c lib_load.h listeners.c diff --git a/src/Makefile b/src/Makefile index e15da600f..0ff54f2e6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,6 +8,7 @@ OBJS= mosquitto.o \ alias_mosq.o \ bridge.o \ bridge_topic.o \ + broker_control.o \ conf.o \ conf_includedir.o \ context.o \ @@ -26,6 +27,7 @@ OBJS= mosquitto.o \ handle_subscribe.o \ handle_unsuback.o \ handle_unsubscribe.o \ + json_help.o \ keepalive.o \ listeners.o \ logging.o \ @@ -98,6 +100,9 @@ bridge.o : bridge.c mosquitto_broker_internal.h bridge_topic.o : bridge_topic.c mosquitto_broker_internal.h ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ +broker_control.o : broker_control.c mosquitto_broker_internal.h + ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ + conf.o : conf.c mosquitto_broker_internal.h ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ @@ -152,6 +157,9 @@ handle_unsuback.o : ../lib/handle_unsuback.c ../lib/read_handle.h handle_unsubscribe.o : handle_unsubscribe.c mosquitto_broker_internal.h ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ +json_help.o : json_help.c json_help.h + ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ + keepalive.o : keepalive.c mosquitto_broker_internal.h ${CROSS_COMPILE}${CC} $(BROKER_CPPFLAGS) $(BROKER_CFLAGS) -c $< -o $@ diff --git a/src/broker_control.c b/src/broker_control.c new file mode 100644 index 000000000..fd61f1f2a --- /dev/null +++ b/src/broker_control.c @@ -0,0 +1,284 @@ +/* +Copyright (c) 2021 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" + +#ifdef WITH_CJSON + +#include +#include +#include +#include +#include +#include +#include + +#include "json_help.h" +#include "mosquitto.h" +#include "mosquitto_broker_internal.h" +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mqtt_protocol.h" + +static mosquitto_plugin_id_t plg_id; + +static int broker__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON *commands); + +void broker__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/broker/v1/response", + (int)payload_len, payload, 0, 0, NULL); +} + + +static int add_plugin_info(cJSON *j_plugins, mosquitto_plugin_id_t *pid) +{ + cJSON *j_plugin, *j_eps, *j_ep; + struct control_endpoint *ep; + + if(pid->plugin_name == NULL){ + return MOSQ_ERR_SUCCESS; + } + + j_plugin = cJSON_CreateObject(); + if(j_plugin == NULL){ + return MOSQ_ERR_NOMEM; + } + + if(cJSON_AddStringToObject(j_plugin, "name", pid->plugin_name) == NULL + || (pid->plugin_version && cJSON_AddStringToObject(j_plugin, "version", pid->plugin_version) == NULL) + || (pid->listener && cJSON_AddNumberToObject(j_plugin, "port", pid->listener->port) == NULL) + || (j_eps = cJSON_AddArrayToObject(j_plugin, "control-endpoints")) == NULL + ){ + + cJSON_Delete(j_plugin); + return MOSQ_ERR_NOMEM; + } + + DL_FOREACH(pid->control_endpoints, ep){ + j_ep = cJSON_CreateString(ep->topic); + if(j_ep == NULL){ + cJSON_Delete(j_plugin); + } + cJSON_AddItemToArray(j_eps, j_ep); + } + + cJSON_AddItemToArray(j_plugins, j_plugin); + return MOSQ_ERR_SUCCESS; +} + + +static int broker__process_get_plugin_info(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) +{ + cJSON *tree, *jtmp, *j_data, *j_plugins; + const char *admin_clientid, *admin_username; + int i; + + UNUSED(command); + + tree = cJSON_CreateObject(); + if(tree == NULL){ + broker__command_reply(j_responses, context, "getPluginInfo", "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, "Broker: %s/%s | getPluginInfo", + admin_clientid, admin_username); + + if(cJSON_AddStringToObject(tree, "command", "getPluginInfo") == NULL + || ((j_data = cJSON_AddObjectToObject(tree, "data")) == NULL) + + ){ + goto internal_error; + } + + j_plugins = cJSON_AddArrayToObject(j_data, "plugins"); + if(j_plugins == NULL){ + goto internal_error; + } + + for(i=0; isecurity_options.auth_plugin_config_count; i++){ + if(add_plugin_info(j_plugins, db.config->security_options.auth_plugin_configs[i].plugin.identifier)){ + 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); + broker__command_reply(j_responses, context, "getPluginInfo", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; +} + + +static int broker_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){ + broker__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); + broker__command_reply(j_responses, ed->client, "Unknown command", "Invalid/missing commands", NULL); + send_response(j_response_tree); + return MOSQ_ERR_SUCCESS; + } + + /* Handle commands */ + broker__handle_control(j_responses, ed->client, commands); + cJSON_Delete(tree); + + send_response(j_response_tree); + + return MOSQ_ERR_SUCCESS; +} + + +int broker_control__init(void) +{ + memset(&plg_id, 0, sizeof(plg_id)); + mosquitto_callback_register(&plg_id, MOSQ_EVT_CONTROL, broker_control_callback, "$CONTROL/broker/v1", NULL); + + return MOSQ_ERR_SUCCESS; +} + +int broker_control__cleanup(void) +{ + mosquitto_callback_unregister(&plg_id, MOSQ_EVT_CONTROL, broker_control_callback, "$CONTROL/broker/v1"); + return MOSQ_ERR_SUCCESS; +} + +/* ################################################################ + * # + * # $CONTROL/broker/v1 handler + * # + * ################################################################ */ + +static int broker__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON *commands) +{ + int rc = MOSQ_ERR_SUCCESS; + cJSON *aiter; + char *command; + char *correlation_data = NULL; + + cJSON_ArrayForEach(aiter, commands){ + if(cJSON_IsObject(aiter)){ + if(json_get_string(aiter, "command", &command, false) == MOSQ_ERR_SUCCESS){ + if(json_get_string(aiter, "correlationData", &correlation_data, true) != MOSQ_ERR_SUCCESS){ + broker__command_reply(j_responses, context, command, "Invalid correlationData data type.", NULL); + return MOSQ_ERR_INVAL; + } + + if(!strcasecmp(command, "getPluginInfo")){ + rc = broker__process_get_plugin_info(j_responses, context, aiter, correlation_data); + + /* Unknown */ + }else{ + broker__command_reply(j_responses, context, command, "Unknown command", correlation_data); + rc = MOSQ_ERR_INVAL; + } + }else{ + broker__command_reply(j_responses, context, "Unknown command", "Missing command", correlation_data); + rc = MOSQ_ERR_INVAL; + } + }else{ + broker__command_reply(j_responses, context, "Unknown command", "Command not an object", correlation_data); + rc = MOSQ_ERR_INVAL; + } + } + + return rc; +} +#endif diff --git a/src/json_help.c b/src/json_help.c new file mode 100644 index 000000000..9761a85f1 --- /dev/null +++ b/src/json_help.c @@ -0,0 +1,106 @@ +/* +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" + +#ifdef WITH_CJSON + +#include +#include +#include +#include + +#include "json_help.h" +#include "mosquitto.h" + + +int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value) +{ + cJSON *jtmp; + + if(optional == true){ + *value = default_value; + } + + jtmp = cJSON_GetObjectItem(json, name); + if(jtmp){ + if(cJSON_IsBool(jtmp) == false){ + return MOSQ_ERR_INVAL; + } + *value = cJSON_IsTrue(jtmp); + }else{ + if(optional == false){ + return MOSQ_ERR_INVAL; + } + } + return MOSQ_ERR_SUCCESS; +} + + +int json_get_int(cJSON *json, const char *name, int *value, bool optional, int default_value) +{ + cJSON *jtmp; + + if(optional == true){ + *value = default_value; + } + + jtmp = cJSON_GetObjectItem(json, name); + if(jtmp){ + if(cJSON_IsNumber(jtmp) == false){ + return MOSQ_ERR_INVAL; + } + *value = jtmp->valueint; + }else{ + if(optional == false){ + return MOSQ_ERR_INVAL; + } + } + return MOSQ_ERR_SUCCESS; +} + + +int json_get_string(cJSON *json, const char *name, char **value, bool optional) +{ + cJSON *jtmp; + + *value = NULL; + + jtmp = cJSON_GetObjectItem(json, name); + if(jtmp){ + if(cJSON_IsString(jtmp) == false){ + return MOSQ_ERR_INVAL; + } + *value = jtmp->valuestring; + }else{ + if(optional == false){ + return MOSQ_ERR_INVAL; + } + } + return MOSQ_ERR_SUCCESS; +} + + +cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, int number) +{ + char buf[30]; + + snprintf(buf, sizeof(buf), "%d", number); + return cJSON_AddRawToObject(object, name, buf); +} +#endif diff --git a/src/json_help.h b/src/json_help.h new file mode 100644 index 000000000..edc4412c4 --- /dev/null +++ b/src/json_help.h @@ -0,0 +1,34 @@ +#ifndef JSON_HELP_H +#define JSON_HELP_H +/* +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. +*/ +#ifdef WITH_CJSON + +#include +#include + +/* "optional==false" can also be taken to mean "only return success if the key exists and is valid" */ +int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value); +int json_get_int(cJSON *json, const char *name, int *value, bool optional, int default_value); +int json_get_string(cJSON *json, const char *name, char **value, bool optional); + +cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, int number); +cJSON *cJSON_CreateInt(int num); + +#endif +#endif diff --git a/src/mosquitto.c b/src/mosquitto.c index 7265c06d3..70c1be29c 100644 --- a/src/mosquitto.c +++ b/src/mosquitto.c @@ -355,6 +355,10 @@ int main(int argc, char *argv[]) bridge__start_all(); #endif +#ifdef WITH_CJSON + broker_control__init(); +#endif + log__printf(NULL, MOSQ_LOG_INFO, "mosquitto version %s running", VERSION); #ifdef WITH_SYSTEMD sd_notify(0, "READY=1"); @@ -365,6 +369,10 @@ int main(int argc, char *argv[]) log__printf(NULL, MOSQ_LOG_INFO, "mosquitto version %s terminating", VERSION); +#ifdef WITH_CJSON + broker_control__cleanup(); +#endif + /* FIXME - this isn't quite right, all wills with will delay zero should be * sent now, but those with positive will delay should be persisted and * restored, pending the client reconnecting in time. */ diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index e20465ad8..8d4fcae7b 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -910,4 +910,9 @@ void will_delay__remove(struct mosquitto *mosq); void xtreport(void); #endif +#ifdef WITH_CJSON +int broker_control__init(void); +int broker_control__cleanup(void); +#endif + #endif