mirror of
https://github.com/eclipse/mosquitto.git
synced 2025-05-09 01:01:11 +08:00
New dynamic security plugin.
This commit is contained in:
parent
e82ee879d5
commit
fdff255916
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,6 +16,7 @@ c/*.test
|
||||
cpp/*.test
|
||||
|
||||
apps/db_dump/mosquitto_db_dump
|
||||
apps/mosquitto_ctrl/mosquitto_ctrl
|
||||
apps/mosquitto_passwd/mosquitto_passwd
|
||||
|
||||
build/
|
||||
@ -44,6 +45,7 @@ man/mosquitto.8
|
||||
man/mosquitto-tls.7
|
||||
man/mosquitto.conf.5
|
||||
man/libmosquitto.3
|
||||
man/mosquitto_ctrl.1
|
||||
man/mosquitto_passwd.1
|
||||
man/mosquitto_pub.1
|
||||
man/mosquitto_rr.1
|
||||
|
@ -97,6 +97,13 @@ if (WITH_DLT)
|
||||
add_definitions("-DWITH_DLT")
|
||||
endif (WITH_DLT)
|
||||
|
||||
FIND_PACKAGE(cJSON)
|
||||
if (CJSON_FOUND)
|
||||
message(STATUS ${CJSON_FOUND})
|
||||
add_definitions("-DWITH_CJSON")
|
||||
endif()
|
||||
|
||||
|
||||
# ========================================
|
||||
# Include projects
|
||||
# ========================================
|
||||
|
@ -1 +1,2 @@
|
||||
add_subdirectory(mosquitto_ctrl)
|
||||
add_subdirectory(mosquitto_passwd)
|
||||
|
@ -1,5 +1,6 @@
|
||||
DIRS= \
|
||||
db_dump \
|
||||
mosquitto_ctrl \
|
||||
mosquitto_passwd
|
||||
|
||||
.PHONY : all binary check clean reallyclean test install uninstall
|
||||
|
29
apps/mosquitto_ctrl/CMakeLists.txt
Normal file
29
apps/mosquitto_ctrl/CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
||||
if (WITH_TLS AND CJSON_FOUND)
|
||||
|
||||
include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/include
|
||||
${mosquitto_SOURCE_DIR}/lib ${mosquitto_SOURCE_DIR}/src
|
||||
${OPENSSL_INCLUDE_DIR} ${STDBOOL_H_PATH} ${STDINT_H_PATH}
|
||||
${CJSON_INCLUDE_DIRS})
|
||||
|
||||
add_executable(mosquitto_ctrl
|
||||
mosquitto_ctrl.c mosquitto_ctrl.h
|
||||
client.c
|
||||
dynsec.c
|
||||
dynsec_client.c
|
||||
dynsec_group.c
|
||||
dynsec_role.c
|
||||
../../lib/memory_mosq.c ../../lib/memory_mosq.h
|
||||
../../src/memory_public.c
|
||||
options.c
|
||||
../../src/password_mosq.c ../../src/password_mosq.h
|
||||
)
|
||||
|
||||
if (WITH_STATIC_LIBRARIES)
|
||||
target_link_libraries(mosquitto_ctrl libmosquitto_static)
|
||||
else()
|
||||
target_link_libraries(mosquitto_ctrl libmosquitto)
|
||||
endif()
|
||||
target_link_libraries(mosquitto_ctrl ${OPENSSL_LIBRARIES} ${CJSON_LIBRARIES})
|
||||
|
||||
install(TARGETS mosquitto_ctrl RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
endif (WITH_TLS AND CJSON_FOUND)
|
79
apps/mosquitto_ctrl/Makefile
Normal file
79
apps/mosquitto_ctrl/Makefile
Normal file
@ -0,0 +1,79 @@
|
||||
include ../../config.mk
|
||||
|
||||
.PHONY: all install uninstall clean reallyclean
|
||||
|
||||
ifeq ($(WITH_SHARED_LIBRARIES),yes)
|
||||
LIBMOSQ:=../../lib/libmosquitto.so.${SOVERSION}
|
||||
else
|
||||
LIBMOSQ:=../../lib/libmosquitto.a
|
||||
endif
|
||||
|
||||
LOCAL_CPPFLAGS:=-I/usr/include/cjson -I/usr/local/include/cjson
|
||||
|
||||
OBJS= mosquitto_ctrl.o \
|
||||
client.o \
|
||||
dynsec.o \
|
||||
dynsec_client.o \
|
||||
dynsec_group.o \
|
||||
dynsec_role.o \
|
||||
memory_mosq.o \
|
||||
memory_public.o \
|
||||
options.o \
|
||||
password_mosq.o
|
||||
|
||||
ifeq ($(WITH_TLS),yes)
|
||||
ifeq ($(WITH_CJSON),yes)
|
||||
TARGET:=mosquitto_ctrl
|
||||
endif
|
||||
endif
|
||||
|
||||
all : $(TARGET)
|
||||
|
||||
mosquitto_ctrl : ${OBJS}
|
||||
${CROSS_COMPILE}${CC} ${APP_LDFLAGS} $^ -o $@ $(PASSWD_LDADD) $(LIBMOSQ) -lcjson
|
||||
|
||||
mosquitto_ctrl.o : mosquitto_ctrl.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 $@
|
||||
|
||||
dynsec.o : dynsec.c mosquitto_ctrl.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
dynsec_client.o : dynsec_client.c mosquitto_ctrl.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
dynsec_group.o : dynsec_group.c mosquitto_ctrl.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
dynsec_role.o : dynsec_role.c mosquitto_ctrl.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
memory_mosq.o : ../../lib/memory_mosq.c
|
||||
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
memory_public.o : ../../src/memory_public.c
|
||||
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
options.o : options.c mosquitto_ctrl.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
misc_mosq.o : ../../lib/misc_mosq.c ../../lib/misc_mosq.h
|
||||
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
password_mosq.o : ../../src/password_mosq.c ../../src/password_mosq.h
|
||||
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
|
||||
|
||||
install : all
|
||||
$(INSTALL) -d "${DESTDIR}$(prefix)/bin"
|
||||
$(INSTALL) ${STRIP_OPTS} mosquitto_ctrl "${DESTDIR}${prefix}/bin/mosquitto_ctrl"
|
||||
|
||||
uninstall :
|
||||
-rm -f "${DESTDIR}${prefix}/bin/mosquitto_ctrl"
|
||||
|
||||
clean :
|
||||
-rm -f *.o mosquitto_ctrl *.gcda *.gcno
|
||||
|
||||
reallyclean : clean
|
||||
-rm -rf *.orig *.db
|
147
apps/mosquitto_ctrl/client.c
Normal file
147
apps/mosquitto_ctrl/client.c
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <mosquitto.h>
|
||||
#include <mqtt_protocol.h>
|
||||
#include "mosquitto_ctrl.h"
|
||||
|
||||
static int run = 1;
|
||||
|
||||
static void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg, const mosquitto_property *properties)
|
||||
{
|
||||
struct mosq_ctrl *ctrl = obj;
|
||||
|
||||
if(ctrl->payload_callback){
|
||||
ctrl->payload_callback(ctrl, msg->payloadlen, msg->payload);
|
||||
}
|
||||
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
run = 0;
|
||||
}
|
||||
|
||||
|
||||
static void on_publish(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties)
|
||||
{
|
||||
if(reason_code > 127){
|
||||
fprintf(stderr, "Publish error: %s\n", mosquitto_reason_string(reason_code));
|
||||
run = 0;
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos, const mosquitto_property *properties)
|
||||
{
|
||||
struct mosq_ctrl *ctrl = obj;
|
||||
char *json_str;
|
||||
|
||||
if(qos_count == 1){
|
||||
if(granted_qos[0] < 128){
|
||||
/* Success */
|
||||
json_str = cJSON_PrintUnformatted(ctrl->j_tree);
|
||||
cJSON_Delete(ctrl->j_tree);
|
||||
ctrl->j_tree = NULL;
|
||||
if(json_str == NULL){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
run = 0;
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
}
|
||||
mosquitto_publish(mosq, NULL, ctrl->request_topic, strlen(json_str), json_str, ctrl->cfg.qos, 0);
|
||||
free(ctrl->request_topic);
|
||||
ctrl->request_topic = NULL;
|
||||
free(json_str);
|
||||
}else{
|
||||
if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){
|
||||
fprintf(stderr, "Subscribe error: %s\n", mosquitto_reason_string(granted_qos[0]));
|
||||
}else{
|
||||
fprintf(stderr, "Subscribe error: Subscription refused.\n");
|
||||
}
|
||||
run = 0;
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
}
|
||||
}else{
|
||||
run = 0;
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void on_connect(struct mosquitto *mosq, void *obj, int reason_code, int flags, const mosquitto_property *properties)
|
||||
{
|
||||
struct mosq_ctrl *ctrl = obj;
|
||||
|
||||
if(reason_code == 0){
|
||||
if(ctrl->response_topic){
|
||||
mosquitto_subscribe(mosq, NULL, ctrl->response_topic, ctrl->cfg.qos);
|
||||
free(ctrl->response_topic);
|
||||
ctrl->response_topic = NULL;
|
||||
}
|
||||
}else{
|
||||
if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){
|
||||
if(reason_code == MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION){
|
||||
fprintf(stderr, "Connection error: %s. Try connecting to an MQTT v5 broker, or use MQTT v3.x mode.\n", mosquitto_reason_string(reason_code));
|
||||
}else{
|
||||
fprintf(stderr, "Connection error: %s\n", mosquitto_reason_string(reason_code));
|
||||
}
|
||||
}else{
|
||||
fprintf(stderr, "Connection error: %s\n", mosquitto_connack_string(reason_code));
|
||||
}
|
||||
run = 0;
|
||||
mosquitto_disconnect_v5(mosq, 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int client_request_response(struct mosq_ctrl *ctrl)
|
||||
{
|
||||
struct mosquitto *mosq;
|
||||
int rc;
|
||||
time_t start;
|
||||
|
||||
mosquitto_lib_init();
|
||||
|
||||
mosq = mosquitto_new(ctrl->cfg.id, true, ctrl);
|
||||
rc = client_opts_set(mosq, &ctrl->cfg);
|
||||
if(rc) goto cleanup;
|
||||
|
||||
mosquitto_connect_v5_callback_set(mosq, on_connect);
|
||||
mosquitto_subscribe_v5_callback_set(mosq, on_subscribe);
|
||||
mosquitto_publish_v5_callback_set(mosq, on_publish);
|
||||
mosquitto_message_v5_callback_set(mosq, on_message);
|
||||
|
||||
rc = client_connect(mosq, &ctrl->cfg);
|
||||
if(rc) goto cleanup;
|
||||
|
||||
start = time(NULL);
|
||||
while(run && start+10 > time(NULL)){
|
||||
mosquitto_loop(mosq, -1, 1);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
mosquitto_destroy(mosq);
|
||||
mosquitto_lib_cleanup();
|
||||
return rc;
|
||||
}
|
505
apps/mosquitto_ctrl/dynsec.c
Normal file
505
apps/mosquitto_ctrl/dynsec.c
Normal file
@ -0,0 +1,505 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mosquitto_ctrl.h"
|
||||
#include "mosquitto.h"
|
||||
#include "password_mosq.h"
|
||||
|
||||
void dynsec__print_usage(void)
|
||||
{
|
||||
printf("\nDynamic Security module\n");
|
||||
printf("=======================\n");
|
||||
printf("\nInitialisation\n--------------\n");
|
||||
printf("Create a new configuration file with an admin user:\n");
|
||||
printf(" mosquitto_ctrl dynsec init <new-config-file> <admin-username> <admin-password> [admin-role]\n");
|
||||
|
||||
printf("\nGeneral\n-------\n");
|
||||
printf("Set ACL default access: setDefaultACLAccess <acltype> allow|deny\n");
|
||||
printf("Set group for anonymous clients: setAnonymousGroup <groupname>\n");
|
||||
|
||||
printf("\nClients\n-------\n");
|
||||
printf("Create a new client: createClient <username> <password>\n");
|
||||
printf("Delete a client: deleteClient <username>\n");
|
||||
printf("Set a client password: setClientPassword <username> <password>\n");
|
||||
printf("Add a role to a client: addClientRole <username> <rolename> [priority]\n");
|
||||
printf(" Higher priority (larger numerical value) roles are evaluated first.\n");
|
||||
printf("Remove role from a client: removeClientRole <username> <rolename>\n");
|
||||
printf("Get client information: getClient <username>\n");
|
||||
printf("List all clients: listClients [count [offset]]\n");
|
||||
|
||||
printf("\nGroups\n------\n");
|
||||
printf("Create a new group: createGroup <groupname>\n");
|
||||
printf("Delete a group: deleteGroup <groupname>\n");
|
||||
printf("Add a role to a group: addGroupRole <groupname> <rolename> [priority]\n");
|
||||
printf(" Higher priority (larger numerical value) roles are evaluated first.\n");
|
||||
printf("Remove role from a group: removeGroupRole <groupname> <rolename>\n");
|
||||
printf("Add client to a group: addGroupClient <groupname> <username> [priority]\n");
|
||||
printf(" Priority sets the group priority for the given client only.\n");
|
||||
printf(" Higher priority (larger numerical value) groups are evaluated first.\n");
|
||||
printf("Remove client from a group: removeGroupClient <groupname> <username>\n");
|
||||
printf("Get group information: getGroup <groupname>\n");
|
||||
printf("List all groups: listGroups [count [offset]]\n");
|
||||
|
||||
printf("\nRoles\n------\n");
|
||||
printf("Create a new role: createRole <rolename>\n");
|
||||
printf("Delete a role: deleteRole <rolename>\n");
|
||||
printf("Add an ACL to a role: addRoleACL <rolename> <aclspec> [priority]\n");
|
||||
printf(" Higher priority (larger numerical value) ACLs are evaluated first.\n");
|
||||
printf("Remove ACL from a role: removeRoleACL <rolename> <aclspec>\n");
|
||||
printf("Get role information: getRole <rolename>\n");
|
||||
printf("List all roles: listRoles [count [offset]]\n");
|
||||
printf("\naclspec: <acltype> <topicFilter> allow|deny\n");
|
||||
printf("acltype: publishClientToBroker|publishBrokerToClient\n");
|
||||
printf(" |subscribeLiteral|subscribePattern\n");
|
||||
printf(" |unsubscribeLiteral|unsubscribePattern\n");
|
||||
}
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Payload callback
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static void print_list(cJSON *j_response, const char *arrayname, const char *keyname)
|
||||
{
|
||||
cJSON *j_data, *j_array, *j_elem, *j_name;
|
||||
|
||||
j_data = cJSON_GetObjectItem(j_response, "data");
|
||||
if(j_data == NULL) return;
|
||||
|
||||
j_array = cJSON_GetObjectItem(j_data, arrayname);
|
||||
if(j_array == NULL || !cJSON_IsArray(j_array)) return;
|
||||
|
||||
cJSON_ArrayForEach(j_elem, j_array){
|
||||
if(cJSON_IsObject(j_elem)){
|
||||
j_name = cJSON_GetObjectItem(j_elem, keyname);
|
||||
if(j_name && cJSON_IsString(j_name)){
|
||||
printf("%s\n", j_name->valuestring);
|
||||
}
|
||||
}else if(cJSON_IsString(j_elem)){
|
||||
printf("%s\n", j_elem->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload)
|
||||
{
|
||||
cJSON *tree, *j_responses, *j_response, *j_command, *j_error;
|
||||
|
||||
tree = cJSON_Parse(payload);
|
||||
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, "listClients")){
|
||||
print_list(j_response, "clients", "username");
|
||||
}else if(!strcasecmp(j_command->valuestring, "listGroups")){
|
||||
print_list(j_response, "groups", "groupname");
|
||||
}else if(!strcasecmp(j_command->valuestring, "listRoles")){
|
||||
print_list(j_response, "roles", "rolename");
|
||||
}else{
|
||||
fprintf(stderr, "%s: Success\n", j_command->valuestring);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(tree);
|
||||
}
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Default ACL access
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int dynsec__default_acl_access(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *acltype, *access;
|
||||
cJSON *j_acls, *j_acl;
|
||||
|
||||
if(argc == 2){
|
||||
acltype = argv[0];
|
||||
access = argv[1];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(strcasecmp(acltype, "publishClientToBroker")
|
||||
&& strcasecmp(acltype, "publishBrokerToClient")
|
||||
&& strcasecmp(acltype, "subscribe")
|
||||
&& strcasecmp(acltype, "unsubscribe")){
|
||||
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(strcasecmp(access, "allow") && strcasecmp(access, "deny")){
|
||||
fprintf(stderr, "Error: access must be \"allow\" or \"deny\".\n");
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "setDefaultACLAccess") == NULL
|
||||
|| (j_acls = cJSON_AddArrayToObject(j_command, "acls")) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
j_acl = cJSON_CreateObject();
|
||||
if(j_acl == NULL){
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToArray(j_acls, j_acl);
|
||||
if(cJSON_AddStringToObject(j_acl, "acltype", acltype) == NULL
|
||||
|| cJSON_AddStringToObject(j_acl, "access", access) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Init
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static cJSON *init_add_acl_to_role(cJSON *j_acls, const char *type, const char *topic)
|
||||
{
|
||||
cJSON *j_acl;
|
||||
|
||||
j_acl = cJSON_CreateObject();
|
||||
if(j_acl == NULL) return NULL;
|
||||
|
||||
if(cJSON_AddStringToObject(j_acl, "acltype", type) == NULL
|
||||
|| cJSON_AddStringToObject(j_acl, "topic", topic) == NULL
|
||||
|| cJSON_AddBoolToObject(j_acl, "allow", true) == NULL
|
||||
){
|
||||
|
||||
cJSON_Delete(j_acl);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(j_acls, j_acl);
|
||||
return j_acl;
|
||||
}
|
||||
|
||||
static cJSON *init_add_role(const char *rolename)
|
||||
{
|
||||
cJSON *j_role, *j_acls;
|
||||
|
||||
j_role = cJSON_CreateObject();
|
||||
if(j_role == NULL){
|
||||
return NULL;
|
||||
}
|
||||
if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){
|
||||
cJSON_Delete(j_role);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j_acls = cJSON_CreateArray();
|
||||
if(j_acls == NULL){
|
||||
cJSON_Delete(j_role);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToObject(j_role, "acls", j_acls);
|
||||
if(init_add_acl_to_role(j_acls, "publishClientToBroker", "$CONTROL/dynamic-security/#") == NULL
|
||||
|| init_add_acl_to_role(j_acls, "publishBrokerToClient", "$CONTROL/dynamic-security/#") == NULL
|
||||
|| init_add_acl_to_role(j_acls, "subscribePattern", "$CONTROL/dynamic-security/#") == NULL
|
||||
|| init_add_acl_to_role(j_acls, "unsubscribePattern", "#") == NULL
|
||||
){
|
||||
|
||||
cJSON_Delete(j_role);
|
||||
return NULL;
|
||||
}
|
||||
return j_role;
|
||||
}
|
||||
|
||||
static cJSON *init_add_client(const char *username, const char *password, const char *rolename)
|
||||
{
|
||||
cJSON *j_client, *j_roles, *j_role;
|
||||
struct mosquitto_pw pw;
|
||||
char *salt64 = NULL, *hash64 = NULL;
|
||||
char buf[10];
|
||||
|
||||
memset(&pw, 0, sizeof(pw));
|
||||
pw.hashtype = pw_sha512_pbkdf2;
|
||||
|
||||
if(pw__hash(password, &pw, true, PW_DEFAULT_ITERATIONS) != 0){
|
||||
return NULL;
|
||||
}
|
||||
if(base64__encode(pw.salt, sizeof(pw.salt), &salt64)
|
||||
|| base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64)
|
||||
){
|
||||
|
||||
fprintf(stderr, "dynsec init: Internal error while encoding password.\n");
|
||||
free(salt64);
|
||||
free(hash64);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j_client = cJSON_CreateObject();
|
||||
if(j_client == NULL) return NULL;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", PW_DEFAULT_ITERATIONS);
|
||||
if(cJSON_AddStringToObject(j_client, "username", username) == NULL
|
||||
|| cJSON_AddStringToObject(j_client, "textName", "Dynsec admin user") == NULL
|
||||
|| cJSON_AddStringToObject(j_client, "password", hash64) == NULL
|
||||
|| cJSON_AddStringToObject(j_client, "salt", salt64) == NULL
|
||||
|| cJSON_AddRawToObject(j_client, "iterations", buf) == NULL
|
||||
){
|
||||
|
||||
free(salt64);
|
||||
free(hash64);
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
free(salt64);
|
||||
free(hash64);
|
||||
|
||||
j_roles = cJSON_CreateArray();
|
||||
if(j_roles == NULL){
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToObject(j_client, "roles", j_roles);
|
||||
|
||||
j_role = cJSON_CreateObject();
|
||||
if(j_role == NULL){
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(j_roles, j_role);
|
||||
if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return j_client;
|
||||
}
|
||||
|
||||
static cJSON *init_create(const char *username, const char *password, const char *rolename)
|
||||
{
|
||||
cJSON *tree, *j_clients, *j_client, *j_roles, *j_role;
|
||||
|
||||
tree = cJSON_CreateObject();
|
||||
if(tree == NULL) return NULL;
|
||||
|
||||
if((j_clients = cJSON_AddArrayToObject(tree, "clients")) == NULL
|
||||
|| (j_roles = cJSON_AddArrayToObject(tree, "roles")) == NULL
|
||||
){
|
||||
|
||||
cJSON_Delete(tree);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j_client = init_add_client(username, password, rolename);
|
||||
if(j_client == NULL){
|
||||
cJSON_Delete(tree);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(j_clients, j_client);
|
||||
|
||||
j_role = init_add_role(rolename);
|
||||
if(j_role == NULL){
|
||||
cJSON_Delete(tree);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(j_roles, j_role);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
/* mosquitto_ctrl dynsec init <filename> <admin-user> <admin-password> [role-name] */
|
||||
int dynsec_init(int argc, char *argv[])
|
||||
{
|
||||
char *filename;
|
||||
char *admin_user;
|
||||
char *admin_password;
|
||||
char *rolename = "admin";
|
||||
char *json_str;
|
||||
cJSON *tree;
|
||||
FILE *fptr;
|
||||
|
||||
if(argc < 3){
|
||||
fprintf(stderr, "dynsec init: Not enough arguments - filename, admin-user or admin-password missing.\n");
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(argc > 4){
|
||||
fprintf(stderr, "dynsec init: Too many arguments.\n");
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
filename = argv[0];
|
||||
admin_user = argv[1];
|
||||
admin_password = argv[2];
|
||||
if(argc == 4){
|
||||
rolename = argv[3];
|
||||
}
|
||||
|
||||
fptr = fopen(filename, "rb");
|
||||
if(fptr){
|
||||
fclose(fptr);
|
||||
fprintf(stderr, "dynsec init: '%s' already exists. Use --force to overwrite.\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tree = init_create(admin_user, admin_password, rolename);
|
||||
if(tree == NULL){
|
||||
fprintf(stderr, "dynsec init: Out of memory.\n");
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
json_str = cJSON_Print(tree);
|
||||
cJSON_Delete(tree);
|
||||
|
||||
fptr = fopen(filename, "wb");
|
||||
if(fptr){
|
||||
fprintf(fptr, "%s", json_str);
|
||||
free(json_str);
|
||||
}else{
|
||||
free(json_str);
|
||||
fprintf(stderr, "dynsec init: Unable to open '%s' for writing.\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1; /* Suppress client connection */
|
||||
}
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Main
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl)
|
||||
{
|
||||
cJSON *j_commands, *j_command;
|
||||
if(!strcasecmp(argv[0], "help")){
|
||||
dynsec__print_usage();
|
||||
return -1;
|
||||
}else if(!strcasecmp(argv[0], "init")){
|
||||
return dynsec_init(argc-1, &argv[1]);
|
||||
}
|
||||
|
||||
/* The remaining commands need a network connection and JSON command. */
|
||||
|
||||
ctrl->payload_callback = dynsec__payload_callback;
|
||||
ctrl->request_topic = strdup("$CONTROL/dynamic-security/v1");
|
||||
ctrl->response_topic = strdup("$CONTROL/dynamic-security/v1/response");
|
||||
if(ctrl->request_topic == NULL || ctrl->response_topic == NULL){
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
ctrl->j_tree = cJSON_CreateObject();
|
||||
if(ctrl->j_tree == NULL) return MOSQ_ERR_NOMEM;
|
||||
j_commands = cJSON_AddArrayToObject(ctrl->j_tree, "commands");
|
||||
if(j_commands == NULL){
|
||||
cJSON_Delete(ctrl->j_tree);
|
||||
ctrl->j_tree = NULL;
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
j_command = cJSON_CreateObject();
|
||||
if(j_command == NULL){
|
||||
cJSON_Delete(ctrl->j_tree);
|
||||
ctrl->j_tree = NULL;
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToArray(j_commands, j_command);
|
||||
|
||||
if(!strcasecmp(argv[0], "setDefaultACLAccess")){
|
||||
return dynsec__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);
|
||||
}else if(!strcasecmp(argv[0], "deleteClient")){
|
||||
return dynsec_client__delete(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "getClient")){
|
||||
return dynsec_client__get(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "listClients")){
|
||||
return dynsec_client__list_all(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "setClientPassword")){
|
||||
return dynsec_client__set_password(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "addClientRole")){
|
||||
return dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
|
||||
}else if(!strcasecmp(argv[0], "removeClientRole")){
|
||||
return dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
|
||||
|
||||
}else if(!strcasecmp(argv[0], "createGroup")){
|
||||
return dynsec_group__create(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "deleteGroup")){
|
||||
return dynsec_group__delete(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "getGroup")){
|
||||
return dynsec_group__get(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "listGroups")){
|
||||
return dynsec_group__list_all(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "addGroupRole")){
|
||||
return dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
|
||||
}else if(!strcasecmp(argv[0], "removeGroupRole")){
|
||||
return dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
|
||||
}else if(!strcasecmp(argv[0], "addGroupClient")){
|
||||
return dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]);
|
||||
}else if(!strcasecmp(argv[0], "removeGroupClient")){
|
||||
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], "createRole")){
|
||||
return dynsec_role__create(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "deleteRole")){
|
||||
return dynsec_role__delete(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "getRole")){
|
||||
return dynsec_role__get(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "listRoles")){
|
||||
return dynsec_role__list_all(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "addRoleACL")){
|
||||
return dynsec_role__add_acl(argc-1, &argv[1], j_command);
|
||||
}else if(!strcasecmp(argv[0], "removeRoleACL")){
|
||||
return dynsec_role__remove_acl(argc-1, &argv[1], j_command);
|
||||
|
||||
}else{
|
||||
fprintf(stderr, "Command '%s' not recognised.\n", argv[0]);
|
||||
return MOSQ_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
163
apps/mosquitto_ctrl/dynsec_client.c
Normal file
163
apps/mosquitto_ctrl/dynsec_client.c
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_ctrl.h"
|
||||
#include "password_mosq.h"
|
||||
|
||||
int dynsec_client__create(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *username = NULL, *password = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
username = argv[0];
|
||||
}else if(argc == 2){
|
||||
username = argv[0];
|
||||
password = argv[1];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "createClient") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
|| (password && cJSON_AddStringToObject(j_command, "password", password) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_client__delete(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *username = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
username = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "deleteClient") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *username = NULL, *password = NULL;
|
||||
|
||||
if(argc == 2){
|
||||
username = argv[0];
|
||||
password = argv[1];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "setClientPassword") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "password", password) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_client__get(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *username = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
username = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "getClient") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command)
|
||||
{
|
||||
char *username = NULL, *rolename = NULL;
|
||||
int priority = -1;
|
||||
|
||||
if(argc == 2){
|
||||
username = argv[0];
|
||||
rolename = argv[1];
|
||||
}else if(argc == 3){
|
||||
username = argv[0];
|
||||
rolename = argv[1];
|
||||
priority = atoi(argv[2]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_client__list_all(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
int count = -1, offset = -1;
|
||||
|
||||
if(argc == 0){
|
||||
/* All clients */
|
||||
}else if(argc == 1){
|
||||
count = atoi(argv[0]);
|
||||
}else if(argc == 2){
|
||||
count = atoi(argv[0]);
|
||||
offset = atoi(argv[1]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "listClients") == NULL
|
||||
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|
||||
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
185
apps/mosquitto_ctrl/dynsec_group.c
Normal file
185
apps/mosquitto_ctrl/dynsec_group.c
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_ctrl.h"
|
||||
#include "password_mosq.h"
|
||||
|
||||
int dynsec_group__create(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *groupname = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
groupname = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "createGroup") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_group__delete(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *groupname = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
groupname = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "deleteGroup") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == 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;
|
||||
|
||||
if(argc == 1){
|
||||
groupname = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "setAnonymousGroup") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_group__get(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *groupname = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
groupname = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "getGroup") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command)
|
||||
{
|
||||
char *groupname = NULL, *rolename = NULL;
|
||||
int priority = -1;
|
||||
|
||||
if(argc == 2){
|
||||
groupname = argv[0];
|
||||
rolename = argv[1];
|
||||
}else if(argc == 3){
|
||||
groupname = argv[0];
|
||||
rolename = argv[1];
|
||||
priority = atoi(argv[2]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
int count = -1, offset = -1;
|
||||
|
||||
if(argc == 0){
|
||||
/* All groups */
|
||||
}else if(argc == 1){
|
||||
count = atoi(argv[0]);
|
||||
}else if(argc == 2){
|
||||
count = atoi(argv[0]);
|
||||
offset = atoi(argv[1]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "listGroups") == NULL
|
||||
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|
||||
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command)
|
||||
{
|
||||
char *username, *groupname;
|
||||
int priority = -1;
|
||||
|
||||
if(argc == 2){
|
||||
username = argv[0];
|
||||
groupname = argv[1];
|
||||
}else if(argc == 3){
|
||||
username = argv[0];
|
||||
groupname = argv[1];
|
||||
priority = atoi(argv[2]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|
||||
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
191
apps/mosquitto_ctrl/dynsec_role.c
Normal file
191
apps/mosquitto_ctrl/dynsec_role.c
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_ctrl.h"
|
||||
#include "password_mosq.h"
|
||||
|
||||
int dynsec_role__create(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *rolename = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
rolename = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "createRole") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_role__delete(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *rolename = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
rolename = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "deleteRole") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_role__get(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *rolename = NULL;
|
||||
|
||||
if(argc == 1){
|
||||
rolename = argv[0];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "getRole") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
int count = -1, offset = -1;
|
||||
|
||||
if(argc == 0){
|
||||
/* All roles */
|
||||
}else if(argc == 1){
|
||||
count = atoi(argv[0]);
|
||||
}else if(argc == 2){
|
||||
count = atoi(argv[0]);
|
||||
offset = atoi(argv[1]);
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "listRoles") == NULL
|
||||
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|
||||
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *rolename, *acltype, *topic, *action;
|
||||
bool allow;
|
||||
int priority = -1;
|
||||
|
||||
if(argc == 5){
|
||||
rolename = argv[0];
|
||||
acltype = argv[1];
|
||||
topic = argv[2];
|
||||
action = argv[3];
|
||||
priority = atoi(argv[4]);
|
||||
}else if(argc == 4){
|
||||
rolename = argv[0];
|
||||
acltype = argv[1];
|
||||
topic = argv[2];
|
||||
action = argv[3];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(strcasecmp(acltype, "publishClientToBroker")
|
||||
&& strcasecmp(acltype, "publishBrokerToClient")
|
||||
&& strcasecmp(acltype, "subscribe")
|
||||
&& strcasecmp(acltype, "unsubscribe")){
|
||||
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
if(!strcasecmp(action, "allow")){
|
||||
allow = true;
|
||||
}else if(!strcasecmp(action, "deny")){
|
||||
allow = false;
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "addACLToRole") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "topic", topic) == NULL
|
||||
|| cJSON_AddBoolToObject(j_command, "allow", allow) == NULL
|
||||
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command)
|
||||
{
|
||||
char *rolename, *acltype, *topic;
|
||||
|
||||
if(argc == 3){
|
||||
rolename = argv[0];
|
||||
acltype = argv[1];
|
||||
topic = argv[2];
|
||||
}else{
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(strcasecmp(acltype, "publishClientToBroker")
|
||||
&& strcasecmp(acltype, "publishBrokerToClient")
|
||||
&& strcasecmp(acltype, "subscribe")
|
||||
&& strcasecmp(acltype, "unsubscribe")){
|
||||
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_command, "command", "removeACLFromRole") == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL
|
||||
|| cJSON_AddStringToObject(j_command, "topic", topic) == NULL
|
||||
){
|
||||
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
90
apps/mosquitto_ctrl/mosquitto_ctrl.c
Normal file
90
apps/mosquitto_ctrl/mosquitto_ctrl.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_ctrl.h"
|
||||
|
||||
void print_version(void)
|
||||
{
|
||||
int major, minor, revision;
|
||||
|
||||
mosquitto_lib_version(&major, &minor, &revision);
|
||||
printf("mosquitto_ctrl version %s running on libmosquitto %d.%d.%d.\n", VERSION, major, minor, revision);
|
||||
}
|
||||
|
||||
void print_usage(void)
|
||||
{
|
||||
printf("mosquitto_ctrl is a tool for administering certain Mosquitto features.\n");
|
||||
print_version();
|
||||
printf("\nGeneral usage: mosquitto_ctrl <module> <module-command> <command-options>\n");
|
||||
printf("For module specific help use: mosquitto_ctrl <module> help\n");
|
||||
printf("\nModules available: dynsec\n");
|
||||
printf("\nSee https://mosquitto.org/man/mosquitto_ctrl-1.html for more information.\n\n");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct mosq_ctrl ctrl;
|
||||
int rc;
|
||||
|
||||
if(argc == 1){
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
init_config(&ctrl.cfg);
|
||||
|
||||
/* Shift program name out of args */
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
ctrl_config_parse(&ctrl.cfg, &argc, &argv);
|
||||
|
||||
if(argc < 2){
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!strcasecmp(argv[0], "dynsec")){
|
||||
rc = dynsec__main(argc-1, &argv[1], &ctrl);
|
||||
if(rc < 0){
|
||||
/* Usage print */
|
||||
rc = 0;
|
||||
}else if(rc == MOSQ_ERR_SUCCESS){
|
||||
rc = client_request_response(&ctrl);
|
||||
}else if(rc == MOSQ_ERR_UNKNOWN){
|
||||
/* Message printed already */
|
||||
}else{
|
||||
fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc));
|
||||
}
|
||||
}else{
|
||||
fprintf(stderr, "Error: Module '%s' not supported.\n", argv[0]);
|
||||
rc = MOSQ_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
client_config_cleanup(&ctrl.cfg);
|
||||
return rc;
|
||||
}
|
109
apps/mosquitto_ctrl/mosquitto_ctrl.h
Normal file
109
apps/mosquitto_ctrl/mosquitto_ctrl.h
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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.
|
||||
*/
|
||||
#ifndef MOSQUITTO_CTRL_H
|
||||
#define MOSQUITTO_CTRL_H
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "mosquitto.h"
|
||||
|
||||
#define PORT_UNDEFINED -1
|
||||
#define PORT_UNIX 0
|
||||
|
||||
struct mosq_config {
|
||||
char *id;
|
||||
int protocol_version;
|
||||
int keepalive;
|
||||
char *host;
|
||||
int port;
|
||||
int qos;
|
||||
char *bind_address;
|
||||
bool debug;
|
||||
bool quiet;
|
||||
char *username;
|
||||
char *password;
|
||||
char *options_file;
|
||||
#ifdef WITH_TLS
|
||||
char *cafile;
|
||||
char *capath;
|
||||
char *certfile;
|
||||
char *keyfile;
|
||||
char *ciphers;
|
||||
bool insecure;
|
||||
char *tls_alpn;
|
||||
char *tls_version;
|
||||
char *tls_engine;
|
||||
char *tls_engine_kpass_sha1;
|
||||
char *keyform;
|
||||
# ifdef FINAL_WITH_TLS_PSK
|
||||
char *psk;
|
||||
char *psk_identity;
|
||||
# endif
|
||||
#endif
|
||||
bool verbose; /* sub */
|
||||
unsigned int timeout; /* sub */
|
||||
#ifdef WITH_SOCKS
|
||||
char *socks5_host;
|
||||
int socks5_port;
|
||||
char *socks5_username;
|
||||
char *socks5_password;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct mosq_ctrl {
|
||||
struct mosq_config cfg;
|
||||
char *request_topic;
|
||||
char *response_topic;
|
||||
cJSON *j_tree;
|
||||
void (*payload_callback)(struct mosq_ctrl *, long , const void *);
|
||||
};
|
||||
|
||||
void init_config(struct mosq_config *cfg);
|
||||
int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[]);
|
||||
int client_config_load(struct mosq_config *cfg);
|
||||
void client_config_cleanup(struct mosq_config *cfg);
|
||||
|
||||
int client_request_response(struct mosq_ctrl *ctrl);
|
||||
int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg);
|
||||
int client_connect(struct mosquitto *mosq, struct mosq_config *cfg);
|
||||
|
||||
void dynsec__print_usage(void);
|
||||
int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl);
|
||||
|
||||
int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command);
|
||||
int dynsec_client__create(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_client__delete(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_client__get(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_client__list_all(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command);
|
||||
|
||||
int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command);
|
||||
int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command);
|
||||
int dynsec_group__create(int argc, char *argv[], cJSON *j_command);
|
||||
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_role__create(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_role__delete(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_role__get(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command);
|
||||
int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command);
|
||||
|
||||
#endif
|
898
apps/mosquitto_ctrl/options.c
Normal file
898
apps/mosquitto_ctrl/options.c
Normal file
@ -0,0 +1,898 @@
|
||||
/*
|
||||
Copyright (c) 2014-2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
#else
|
||||
#include <process.h>
|
||||
#include <winsock2.h>
|
||||
#define snprintf sprintf_s
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
|
||||
#include <mosquitto.h>
|
||||
#include <mqtt_protocol.h>
|
||||
#include "mosquitto_ctrl.h"
|
||||
|
||||
#ifdef WITH_SOCKS
|
||||
static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url);
|
||||
#endif
|
||||
static int client_config_line_proc(struct mosq_config *cfg, int *argc, char **argvp[]);
|
||||
|
||||
|
||||
void init_config(struct mosq_config *cfg)
|
||||
{
|
||||
cfg->qos = 1;
|
||||
cfg->port = PORT_UNDEFINED;
|
||||
cfg->protocol_version = MQTT_PROTOCOL_V5;
|
||||
}
|
||||
|
||||
void client_config_cleanup(struct mosq_config *cfg)
|
||||
{
|
||||
free(cfg->id);
|
||||
free(cfg->host);
|
||||
free(cfg->bind_address);
|
||||
free(cfg->username);
|
||||
free(cfg->password);
|
||||
free(cfg->options_file);
|
||||
#ifdef WITH_TLS
|
||||
free(cfg->cafile);
|
||||
free(cfg->capath);
|
||||
free(cfg->certfile);
|
||||
free(cfg->keyfile);
|
||||
free(cfg->ciphers);
|
||||
free(cfg->tls_alpn);
|
||||
free(cfg->tls_version);
|
||||
free(cfg->tls_engine);
|
||||
free(cfg->tls_engine_kpass_sha1);
|
||||
free(cfg->keyform);
|
||||
# ifdef FINAL_WITH_TLS_PSK
|
||||
free(cfg->psk);
|
||||
free(cfg->psk_identity);
|
||||
# endif
|
||||
#endif
|
||||
#ifdef WITH_SOCKS
|
||||
free(cfg->socks5_host);
|
||||
free(cfg->socks5_username);
|
||||
free(cfg->socks5_password);
|
||||
#endif
|
||||
}
|
||||
|
||||
int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[])
|
||||
{
|
||||
int rc;
|
||||
|
||||
init_config(cfg);
|
||||
|
||||
/* Deal with real argc/argv */
|
||||
rc = client_config_line_proc(cfg, argc, argv);
|
||||
if(rc) return rc;
|
||||
|
||||
rc = client_config_load(cfg);
|
||||
if(rc) return rc;
|
||||
|
||||
#ifdef WITH_TLS
|
||||
if((cfg->certfile && !cfg->keyfile) || (cfg->keyfile && !cfg->certfile)){
|
||||
fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is set.\n");
|
||||
return 1;
|
||||
}
|
||||
if((cfg->keyform && !cfg->keyfile)){
|
||||
fprintf(stderr, "Error: If keyform is set, keyfile must be also specified.\n");
|
||||
return 1;
|
||||
}
|
||||
if((cfg->tls_engine_kpass_sha1 && (!cfg->keyform || !cfg->tls_engine))){
|
||||
fprintf(stderr, "Error: when using tls-engine-kpass-sha1, both tls-engine and keyform must also be provided.\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
#ifdef FINAL_WITH_TLS_PSK
|
||||
if((cfg->cafile || cfg->capath) && cfg->psk){
|
||||
fprintf(stderr, "Error: Only one of --psk or --cafile/--capath may be used at once.\n");
|
||||
return 1;
|
||||
}
|
||||
if(cfg->psk && !cfg->psk_identity){
|
||||
fprintf(stderr, "Error: --psk-identity required if --psk used.\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!cfg->host){
|
||||
cfg->host = strdup("localhost");
|
||||
if(!cfg->host){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/* Process a tokenised single line from a file or set of real argc/argv */
|
||||
static int client_config_line_proc(struct mosq_config *cfg, int *argc, char **argvp[])
|
||||
{
|
||||
char **argv = *argvp;
|
||||
|
||||
while((*argc) && argv[0][0] == '-'){
|
||||
if(!strcmp(argv[0], "-A")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -A argument given but no address specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->bind_address = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#ifdef WITH_TLS
|
||||
}else if(!strcmp(argv[0], "--cafile")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --cafile argument given but no file specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->cafile = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--capath")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --capath argument given but no directory specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->capath = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--cert")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --cert argument given but no file specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->certfile = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--ciphers")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --ciphers argument given but no ciphers specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->ciphers = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#endif
|
||||
}else if(!strcmp(argv[0], "-d") || !strcmp(argv[0], "--debug")){
|
||||
cfg->debug = true;
|
||||
}else if(!strcmp(argv[0], "--help")){
|
||||
return 2;
|
||||
}else if(!strcmp(argv[0], "-h") || !strcmp(argv[0], "--host")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -h argument given but no host specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->host = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#ifdef WITH_TLS
|
||||
}else if(!strcmp(argv[0], "--insecure")){
|
||||
cfg->insecure = true;
|
||||
#endif
|
||||
}else if(!strcmp(argv[0], "-i") || !strcmp(argv[0], "--id")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -i argument given but no id specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->id = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#ifdef WITH_TLS
|
||||
}else if(!strcmp(argv[0], "--key")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --key argument given but no file specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->keyfile = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--keyform")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --keyform argument given but no keyform specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->keyform = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#endif
|
||||
}else if(!strcmp(argv[0], "-L") || !strcmp(argv[0], "--url")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -L argument given but no URL specified.\n\n");
|
||||
return 1;
|
||||
} else {
|
||||
char *url = argv[1];
|
||||
char *topic;
|
||||
char *tmp;
|
||||
|
||||
if(!strncasecmp(url, "mqtt://", 7)) {
|
||||
url += 7;
|
||||
cfg->port = 1883;
|
||||
} else if(!strncasecmp(url, "mqtts://", 8)) {
|
||||
url += 8;
|
||||
cfg->port = 8883;
|
||||
} else {
|
||||
fprintf(stderr, "Error: unsupported URL scheme.\n\n");
|
||||
return 1;
|
||||
}
|
||||
topic = strchr(url, '/');
|
||||
if(!topic){
|
||||
fprintf(stderr, "Error: Invalid URL for -L argument specified - topic missing.\n");
|
||||
return 1;
|
||||
}
|
||||
*topic++ = 0;
|
||||
|
||||
tmp = strchr(url, '@');
|
||||
if(tmp) {
|
||||
*tmp++ = 0;
|
||||
char *colon = strchr(url, ':');
|
||||
if(colon) {
|
||||
*colon = 0;
|
||||
cfg->password = strdup(colon + 1);
|
||||
}
|
||||
cfg->username = strdup(url);
|
||||
url = tmp;
|
||||
}
|
||||
cfg->host = url;
|
||||
|
||||
tmp = strchr(url, ':');
|
||||
if(tmp) {
|
||||
*tmp++ = 0;
|
||||
cfg->port = atoi(tmp);
|
||||
}
|
||||
/* Now we've removed the port, time to get the host on the heap */
|
||||
cfg->host = strdup(cfg->host);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "-o")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -o argument given but no options file specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->options_file = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "-p") || !strcmp(argv[0], "--port")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -p argument given but no port specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->port = atoi(argv[1]);
|
||||
if(cfg->port<0 || cfg->port>65535){
|
||||
fprintf(stderr, "Error: Invalid port given: %d\n", cfg->port);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "-P") || !strcmp(argv[0], "--pw")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -P argument given but no password specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->password = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#ifdef WITH_SOCKS
|
||||
}else if(!strcmp(argv[0], "--proxy")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --proxy argument given but no proxy url specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
if(mosquitto__parse_socks_url(cfg, argv[1])){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#endif
|
||||
#ifdef FINAL_WITH_TLS_PSK
|
||||
}else if(!strcmp(argv[0], "--psk")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --psk argument given but no key specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->psk = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--psk-identity")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --psk-identity argument given but no identity specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->psk_identity = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#endif
|
||||
}else if(!strcmp(argv[0], "-q") || !strcmp(argv[0], "--qos")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->qos = atoi(argv[1]);
|
||||
if(cfg->qos<0 || cfg->qos>2){
|
||||
fprintf(stderr, "Error: Invalid QoS given: %d\n", cfg->qos);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--quiet")){
|
||||
cfg->quiet = true;
|
||||
#ifdef WITH_TLS
|
||||
}else if(!strcmp(argv[0], "--tls-alpn")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --tls-alpn argument given but no protocol specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->tls_alpn = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--tls-engine")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --tls-engine argument given but no engine_id specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->tls_engine = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--tls-engine-kpass-sha1")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --tls-engine-kpass-sha1 argument given but no kpass sha1 specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->tls_engine_kpass_sha1 = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--tls-version")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --tls-version argument given but no version specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->tls_version = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
#endif
|
||||
}else if(!strcmp(argv[0], "-u") || !strcmp(argv[0], "--username")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: -u argument given but no username specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->username = strdup(argv[1]);
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "--unix")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --unix argument given but no socket path specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
cfg->host = strdup(argv[1]);
|
||||
cfg->port = 0;
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "-V") || !strcmp(argv[0], "--protocol-version")){
|
||||
if((*argc) == 1){
|
||||
fprintf(stderr, "Error: --protocol-version argument given but no version specified.\n\n");
|
||||
return 1;
|
||||
}else{
|
||||
if(!strcmp(argv[1], "mqttv31") || !strcmp(argv[1], "31")){
|
||||
cfg->protocol_version = MQTT_PROTOCOL_V31;
|
||||
}else if(!strcmp(argv[1], "mqttv311") || !strcmp(argv[1], "311")){
|
||||
cfg->protocol_version = MQTT_PROTOCOL_V311;
|
||||
}else if(!strcmp(argv[1], "mqttv5") || !strcmp(argv[1], "5")){
|
||||
cfg->protocol_version = MQTT_PROTOCOL_V5;
|
||||
}else{
|
||||
fprintf(stderr, "Error: Invalid protocol version argument given.\n\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}else if(!strcmp(argv[0], "-v") || !strcmp(argv[0], "--verbose")){
|
||||
cfg->verbose = 1;
|
||||
}else if(!strcmp(argv[0], "--version")){
|
||||
return 3;
|
||||
}else{
|
||||
goto unknown_option;
|
||||
}
|
||||
argv++;
|
||||
(*argc)--;
|
||||
}
|
||||
*argvp = argv;
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
|
||||
unknown_option:
|
||||
fprintf(stderr, "Error: Unknown option '%s'.\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *get_default_cfg_location(void)
|
||||
{
|
||||
char *loc = NULL;
|
||||
size_t len;
|
||||
#ifndef WIN32
|
||||
char *env;
|
||||
#else
|
||||
char env[1024];
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
env = getenv("XDG_CONFIG_HOME");
|
||||
if(env){
|
||||
len = strlen(env) + strlen("/mosquitto_ctrl") + 1;
|
||||
loc = malloc(len);
|
||||
if(!loc){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
snprintf(loc, len, "%s/mosquitto_ctrl", env);
|
||||
loc[len-1] = '\0';
|
||||
}else{
|
||||
env = getenv("HOME");
|
||||
if(env){
|
||||
len = strlen(env) + strlen("/.config/mosquitto_ctrl") + 1;
|
||||
loc = malloc(len);
|
||||
if(!loc){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
snprintf(loc, len, "%s/.config/mosquitto_ctrl", env);
|
||||
loc[len-1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
rc = GetEnvironmentVariable("USERPROFILE", env, 1024);
|
||||
if(rc > 0 && rc < 1024){
|
||||
len = strlen(env) + strlen("\\mosquitto_ctrl.conf") + 1;
|
||||
loc = malloc(len);
|
||||
if(!loc){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
snprintf(loc, len, "%s\\mosquitto_ctrl.conf", env);
|
||||
loc[len-1] = '\0';
|
||||
}
|
||||
#endif
|
||||
return loc;
|
||||
}
|
||||
|
||||
int client_config_load(struct mosq_config *cfg)
|
||||
{
|
||||
int rc;
|
||||
FILE *fptr = NULL;
|
||||
char line[1024];
|
||||
int count;
|
||||
char **local_args, **args;
|
||||
char *default_cfg;
|
||||
|
||||
if(cfg->options_file){
|
||||
fptr = fopen(cfg->options_file, "rt");
|
||||
}else{
|
||||
default_cfg = get_default_cfg_location();
|
||||
if(default_cfg){
|
||||
fptr = fopen(default_cfg, "rt");
|
||||
free(default_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
if(fptr){
|
||||
local_args = malloc(3*sizeof(char *));
|
||||
if(local_args == NULL){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
return 1;
|
||||
}
|
||||
while(fgets(line, 1024, fptr)){
|
||||
if(line[0] == '#') continue; /* Comments */
|
||||
|
||||
while(line[strlen(line)-1] == 10 || line[strlen(line)-1] == 13){
|
||||
line[strlen(line)-1] = 0;
|
||||
}
|
||||
local_args[0] = strtok(line, " ");
|
||||
if(local_args[0]){
|
||||
local_args[1] = strtok(NULL, " ");
|
||||
if(local_args[1]){
|
||||
count = 2;
|
||||
}else{
|
||||
count = 1;
|
||||
}
|
||||
args = local_args;
|
||||
rc = client_config_line_proc(cfg, &count, &args);
|
||||
if(rc){
|
||||
fclose(fptr);
|
||||
free(local_args);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fptr);
|
||||
free(local_args);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg)
|
||||
{
|
||||
#if defined(WITH_TLS) || defined(WITH_SOCKS)
|
||||
int rc;
|
||||
#endif
|
||||
|
||||
mosquitto_int_option(mosq, MOSQ_OPT_PROTOCOL_VERSION, cfg->protocol_version);
|
||||
|
||||
if((cfg->username || cfg->password) && mosquitto_username_pw_set(mosq, cfg->username, cfg->password)){
|
||||
fprintf(stderr, "Error: Problem setting username and/or password.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
#ifdef WITH_TLS
|
||||
if(cfg->cafile || cfg->capath){
|
||||
rc = mosquitto_tls_set(mosq, cfg->cafile, cfg->capath, cfg->certfile, cfg->keyfile, NULL);
|
||||
if(rc){
|
||||
if(rc == MOSQ_ERR_INVAL){
|
||||
fprintf(stderr, "Error: Problem setting TLS options: File not found.\n");
|
||||
}else{
|
||||
fprintf(stderr, "Error: Problem setting TLS options: %s.\n", mosquitto_strerror(rc));
|
||||
}
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if(cfg->insecure && mosquitto_tls_insecure_set(mosq, true)){
|
||||
fprintf(stderr, "Error: Problem setting TLS insecure option.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if(cfg->tls_engine && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE, cfg->tls_engine)){
|
||||
fprintf(stderr, "Error: Problem setting TLS engine, is %s a valid engine?\n", cfg->tls_engine);
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){
|
||||
fprintf(stderr, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if(cfg->tls_engine_kpass_sha1 && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE_KPASS_SHA1, cfg->tls_engine_kpass_sha1)){
|
||||
fprintf(stderr, "Error: Problem setting TLS engine key pass sha, is it a 40 character hex string?\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if(cfg->tls_alpn && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ALPN, cfg->tls_alpn)){
|
||||
fprintf(stderr, "Error: Problem setting TLS ALPN protocol.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
# ifdef FINAL_WITH_TLS_PSK
|
||||
if(cfg->psk && mosquitto_tls_psk_set(mosq, cfg->psk, cfg->psk_identity, NULL)){
|
||||
fprintf(stderr, "Error: Problem setting TLS-PSK options.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
# endif
|
||||
if((cfg->tls_version || cfg->ciphers) && mosquitto_tls_opts_set(mosq, 1, cfg->tls_version, cfg->ciphers)){
|
||||
fprintf(stderr, "Error: Problem setting TLS options, check the options are valid.\n");
|
||||
mosquitto_lib_cleanup();
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_SOCKS
|
||||
if(cfg->socks5_host){
|
||||
rc = mosquitto_socks5_set(mosq, cfg->socks5_host, cfg->socks5_port, cfg->socks5_username, cfg->socks5_password);
|
||||
if(rc){
|
||||
mosquitto_lib_cleanup();
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int client_connect(struct mosquitto *mosq, struct mosq_config *cfg)
|
||||
{
|
||||
#ifndef WIN32
|
||||
char *err;
|
||||
#else
|
||||
char err[1024];
|
||||
#endif
|
||||
int rc;
|
||||
int port;
|
||||
|
||||
if(cfg->port == PORT_UNDEFINED){
|
||||
#ifdef WITH_TLS
|
||||
if(cfg->cafile || cfg->capath
|
||||
# ifdef FINAL_WITH_TLS_PSK
|
||||
|| cfg->psk
|
||||
# endif
|
||||
){
|
||||
port = 8883;
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
port = 1883;
|
||||
}
|
||||
}else{
|
||||
port = cfg->port;
|
||||
}
|
||||
|
||||
rc = mosquitto_connect_bind_v5(mosq, cfg->host, port, 60, cfg->bind_address, NULL);
|
||||
if(rc>0){
|
||||
if(rc == MOSQ_ERR_ERRNO){
|
||||
#ifndef WIN32
|
||||
err = strerror(errno);
|
||||
#else
|
||||
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL);
|
||||
#endif
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
}else{
|
||||
fprintf(stderr, "Unable to connect (%s).\n", mosquitto_strerror(rc));
|
||||
}
|
||||
mosquitto_lib_cleanup();
|
||||
return rc;
|
||||
}
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
#ifdef WITH_SOCKS
|
||||
/* Convert %25 -> %, %3a, %3A -> :, %40 -> @ */
|
||||
static int mosquitto__urldecode(char *str)
|
||||
{
|
||||
int i, j;
|
||||
size_t len;
|
||||
if(!str) return 0;
|
||||
|
||||
if(!strchr(str, '%')) return 0;
|
||||
|
||||
len = strlen(str);
|
||||
for(i=0; i<len; i++){
|
||||
if(str[i] == '%'){
|
||||
if(i+2 >= len){
|
||||
return 1;
|
||||
}
|
||||
if(str[i+1] == '2' && str[i+2] == '5'){
|
||||
str[i] = '%';
|
||||
len -= 2;
|
||||
for(j=i+1; j<len; j++){
|
||||
str[j] = str[j+2];
|
||||
}
|
||||
str[j] = '\0';
|
||||
}else if(str[i+1] == '3' && (str[i+2] == 'A' || str[i+2] == 'a')){
|
||||
str[i] = ':';
|
||||
len -= 2;
|
||||
for(j=i+1; j<len; j++){
|
||||
str[j] = str[j+2];
|
||||
}
|
||||
str[j] = '\0';
|
||||
}else if(str[i+1] == '4' && str[i+2] == '0'){
|
||||
str[i] = ':';
|
||||
len -= 2;
|
||||
for(j=i+1; j<len; j++){
|
||||
str[j] = str[j+2];
|
||||
}
|
||||
str[j] = '\0';
|
||||
}else{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url)
|
||||
{
|
||||
char *str;
|
||||
size_t i;
|
||||
char *username = NULL, *password = NULL, *host = NULL, *port = NULL;
|
||||
char *username_or_host = NULL;
|
||||
size_t start;
|
||||
size_t len;
|
||||
bool have_auth = false;
|
||||
int port_int;
|
||||
|
||||
if(!strncmp(url, "socks5h://", strlen("socks5h://"))){
|
||||
str = url + strlen("socks5h://");
|
||||
}else{
|
||||
fprintf(stderr, "Error: Unsupported proxy protocol: %s\n", url);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// socks5h://username:password@host:1883
|
||||
// socks5h://username:password@host
|
||||
// socks5h://username@host:1883
|
||||
// socks5h://username@host
|
||||
// socks5h://host:1883
|
||||
// socks5h://host
|
||||
|
||||
start = 0;
|
||||
for(i=0; i<strlen(str); i++){
|
||||
if(str[i] == ':'){
|
||||
if(i == start){
|
||||
goto cleanup;
|
||||
}
|
||||
if(have_auth){
|
||||
/* Have already seen a @ , so this must be of form
|
||||
* socks5h://username[:password]@host:port */
|
||||
if(host){
|
||||
/* Already seen a host, must be malformed. */
|
||||
goto cleanup;
|
||||
}
|
||||
len = i-start;
|
||||
host = malloc(len + 1);
|
||||
if(!host){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(host, &(str[start]), len);
|
||||
host[len] = '\0';
|
||||
start = i+1;
|
||||
}else if(!username_or_host){
|
||||
/* Haven't seen a @ before, so must be of form
|
||||
* socks5h://host:port or
|
||||
* socks5h://username:password@host[:port] */
|
||||
len = i-start;
|
||||
username_or_host = malloc(len + 1);
|
||||
if(!username_or_host){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(username_or_host, &(str[start]), len);
|
||||
username_or_host[len] = '\0';
|
||||
start = i+1;
|
||||
}
|
||||
}else if(str[i] == '@'){
|
||||
if(i == start){
|
||||
goto cleanup;
|
||||
}
|
||||
have_auth = true;
|
||||
if(username_or_host){
|
||||
/* Must be of form socks5h://username:password@... */
|
||||
username = username_or_host;
|
||||
username_or_host = NULL;
|
||||
|
||||
len = i-start;
|
||||
password = malloc(len + 1);
|
||||
if(!password){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(password, &(str[start]), len);
|
||||
password[len] = '\0';
|
||||
start = i+1;
|
||||
}else{
|
||||
/* Haven't seen a : yet, so must be of form
|
||||
* socks5h://username@... */
|
||||
if(username){
|
||||
/* Already got a username, must be malformed. */
|
||||
goto cleanup;
|
||||
}
|
||||
len = i-start;
|
||||
username = malloc(len + 1);
|
||||
if(!username){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(username, &(str[start]), len);
|
||||
username[len] = '\0';
|
||||
start = i+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Deal with remainder */
|
||||
if(i > start){
|
||||
len = i-start;
|
||||
if(host){
|
||||
/* Have already seen a @ , so this must be of form
|
||||
* socks5h://username[:password]@host:port */
|
||||
port = malloc(len + 1);
|
||||
if(!port){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(port, &(str[start]), len);
|
||||
port[len] = '\0';
|
||||
}else if(username_or_host){
|
||||
/* Haven't seen a @ before, so must be of form
|
||||
* socks5h://host:port */
|
||||
host = username_or_host;
|
||||
username_or_host = NULL;
|
||||
port = malloc(len + 1);
|
||||
if(!port){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(port, &(str[start]), len);
|
||||
port[len] = '\0';
|
||||
}else{
|
||||
host = malloc(len + 1);
|
||||
if(!host){
|
||||
fprintf(stderr, "Error: Out of memory.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
memcpy(host, &(str[start]), len);
|
||||
host[len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if(!host){
|
||||
fprintf(stderr, "Error: Invalid proxy.\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if(mosquitto__urldecode(username)){
|
||||
goto cleanup;
|
||||
}
|
||||
if(mosquitto__urldecode(password)){
|
||||
goto cleanup;
|
||||
}
|
||||
if(port){
|
||||
port_int = atoi(port);
|
||||
if(port_int < 1 || port_int > 65535){
|
||||
fprintf(stderr, "Error: Invalid proxy port %d\n", port_int);
|
||||
goto cleanup;
|
||||
}
|
||||
free(port);
|
||||
}else{
|
||||
port_int = 1080;
|
||||
}
|
||||
|
||||
cfg->socks5_username = username;
|
||||
cfg->socks5_password = password;
|
||||
cfg->socks5_host = host;
|
||||
cfg->socks5_port = port_int;
|
||||
|
||||
return 0;
|
||||
cleanup:
|
||||
free(username_or_host);
|
||||
free(username);
|
||||
free(password);
|
||||
free(host);
|
||||
free(port);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
@ -1,5 +1,3 @@
|
||||
FIND_PACKAGE(cJSON)
|
||||
|
||||
set(shared_src client_shared.c client_shared.h client_props.c)
|
||||
|
||||
if (WITH_SRV)
|
||||
@ -13,10 +11,8 @@ set( CLIENT_INC ${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/include
|
||||
set( CLIENT_DIR ${mosquitto_BINARY_DIR}/lib)
|
||||
|
||||
if (CJSON_FOUND)
|
||||
message(STATUS ${CJSON_FOUND})
|
||||
set( CLIENT_DIR "${CLIENT_DIR} ${CJSON_DIR}" )
|
||||
set( CLIENT_INC "${CLIENT_INC};${CJSON_INCLUDE_DIRS}" )
|
||||
add_definitions("-DWITH_CJSON")
|
||||
endif()
|
||||
|
||||
include_directories(${CLIENT_INC})
|
||||
|
@ -27,7 +27,7 @@ m - PUB,RR (message input)
|
||||
N - RR,SUB (no end of line)
|
||||
n - PUB,RR (null message)
|
||||
O
|
||||
o
|
||||
o - CTRL (options file)
|
||||
P - PUB,RR,SUB (password)
|
||||
p - PUB,RR,SUB (port)
|
||||
Q
|
||||
|
@ -12,6 +12,7 @@ FIND_PATH(
|
||||
HINTS
|
||||
CJSON_DIR
|
||||
/usr/include/cjson
|
||||
/usr/local/include/cjson
|
||||
)
|
||||
|
||||
FIND_LIBRARY( CJSON_LIBRARY
|
||||
|
14
config.mk
14
config.mk
@ -145,21 +145,19 @@ endif
|
||||
STATIC_LIB_DEPS:=
|
||||
|
||||
APP_CPPFLAGS=$(CPPFLAGS) -I. -I../../ -I../../include -I../../src -I../../lib
|
||||
APP_CFLAGS=$(CFLAGS)
|
||||
APP_CFLAGS=$(CFLAGS) -DVERSION=\""${VERSION}\""
|
||||
APP_LDFLAGS:=$(LDFLAGS)
|
||||
|
||||
LIB_CPPFLAGS=$(CPPFLAGS) -I. -I.. -I../include -I../../include
|
||||
ifeq ($(WITH_BUNDLED_DEPS),yes)
|
||||
LIB_CPPFLAGS:=$(LIB_CPPFLAGS) -I../deps
|
||||
endif
|
||||
LIB_CFLAGS:=$(CFLAGS)
|
||||
LIB_CXXFLAGS:=$(CXXFLAGS)
|
||||
LIB_LDFLAGS:=$(LDFLAGS)
|
||||
LIB_LIBADD:=$(LIBADD)
|
||||
|
||||
BROKER_CPPFLAGS:=$(LIB_CPPFLAGS) -I../lib -I/usr/include/cjson -I/usr/local/include/cjson
|
||||
BROKER_CPPFLAGS:=$(LIB_CPPFLAGS) -I../lib
|
||||
BROKER_CFLAGS:=${CFLAGS} -DVERSION="\"${VERSION}\"" -DWITH_BROKER
|
||||
BROKER_LDFLAGS:=${LDFLAGS}
|
||||
BROKER_LDADD:=-lcjson
|
||||
BROKER_LDADD:=
|
||||
|
||||
CLIENT_CPPFLAGS:=$(CPPFLAGS) -I.. -I../include
|
||||
CLIENT_CFLAGS:=${CFLAGS} -DVERSION="\"${VERSION}\""
|
||||
@ -169,6 +167,8 @@ CLIENT_LDADD:=
|
||||
PASSWD_LDADD:=
|
||||
|
||||
PLUGIN_CPPFLAGS:=$(CPPFLAGS) -I../.. -I../../include
|
||||
PLUGIN_CFLAGS:=$(CFLAGS) -fPIC
|
||||
PLUGIN_LDFLAGS:=$(LDFLAGS)
|
||||
|
||||
ifneq ($(or $(findstring $(UNAME),FreeBSD), $(findstring $(UNAME),OpenBSD), $(findstring $(UNAME),NetBSD)),)
|
||||
BROKER_LDADD:=$(BROKER_LDADD) -lm
|
||||
@ -337,6 +337,8 @@ endif
|
||||
|
||||
ifeq ($(WITH_BUNDLED_DEPS),yes)
|
||||
BROKER_CPPFLAGS:=$(BROKER_CPPFLAGS) -I../deps
|
||||
LIB_CPPFLAGS:=$(LIB_CPPFLAGS) -I../deps
|
||||
PLUGIN_CPPFLAGS:=$(PLUGIN_CPPFLAGS) -I../../deps
|
||||
endif
|
||||
|
||||
ifeq ($(WITH_COVERAGE),yes)
|
||||
|
@ -3,7 +3,8 @@ FROM alpine:3.12
|
||||
LABEL maintainer="Roger Light <roger@atchoo.org>" \
|
||||
description="Eclipse Mosquitto MQTT Broker"
|
||||
|
||||
ENV LWS_VERSION=2.4.2
|
||||
ENV LWS_VERSION=2.4.2 \
|
||||
CJSON_VERSION=1.7.14
|
||||
|
||||
COPY mosq.tar.gz /tmp
|
||||
|
||||
@ -32,13 +33,20 @@ RUN set -x && \
|
||||
-DLWS_WITH_ZLIB=OFF && \
|
||||
make -j "$(nproc)" && \
|
||||
rm -rf /root/.cmake && \
|
||||
wget https://github.com/DaveGamble/cJSON/archive/v${CJSON_VERSION}.tar.gz -O /tmp/cjson.tar.gz && \
|
||||
mkdir -p /build/cjson && \
|
||||
tar --strip=1 -xf /tmp/cjson.tar.gz -C /build/cjson && \
|
||||
rm /tmp/cjson.tar.gz && \
|
||||
cd /build/cjson && \
|
||||
make -j "$(nproc)" libcjson.a && \
|
||||
mkdir -p /build/mosq && \
|
||||
tar --strip=1 -xf /tmp/mosq.tar.gz -C /build/mosq && \
|
||||
rm /tmp/mosq.tar.gz && \
|
||||
make -C /build/mosq -j "$(nproc)" \
|
||||
CFLAGS="-Wall -O2 -I/build/lws/include" \
|
||||
LDFLAGS="-L/build/lws/lib" \
|
||||
CFLAGS="-Wall -O2 -I/build/lws/include -I/build/cjson" \
|
||||
LDFLAGS="-L/build/lws/lib -L/build/cjson" \
|
||||
WITH_ADNS=no \
|
||||
WITH_CJSON=yes \
|
||||
WITH_DOCS=no \
|
||||
WITH_SHARED_LIBRARIES=yes \
|
||||
WITH_SRV=no \
|
||||
@ -55,7 +63,9 @@ RUN set -x && \
|
||||
install -s -m755 /build/mosq/client/mosquitto_sub /usr/bin/mosquitto_sub && \
|
||||
install -s -m644 /build/mosq/lib/libmosquitto.so.1 /usr/lib/libmosquitto.so.1 && \
|
||||
install -s -m755 /build/mosq/src/mosquitto /usr/sbin/mosquitto && \
|
||||
install -s -m755 /build/mosq/src/mosquitto_passwd /usr/bin/mosquitto_passwd && \
|
||||
install -s -m755 /build/mosq/apps/mosquitto_passwd/mosquitto_passwd /usr/bin/mosquitto_passwd && \
|
||||
install -s -m755 /build/mosq/apps/mosquitto_ctrl/mosquitto_ctrl /usr/bin/mosquitto_ctrl && \
|
||||
install -s -m755 /build/mosq/plugins/dynamic-security/mosquitto_dynamic_security.so /usr/lib/mosquitto_dynamic_security.so && \
|
||||
install -m644 /build/mosq/mosquitto.conf /mosquitto/config/mosquitto.conf && \
|
||||
chown -R mosquitto:mosquitto /mosquitto && \
|
||||
apk --no-cache add \
|
||||
|
@ -7,6 +7,7 @@ MANPAGES = \
|
||||
mosquitto-tls.7 \
|
||||
mosquitto.8 \
|
||||
mosquitto.conf.5 \
|
||||
mosquitto_ctrl.1 \
|
||||
mosquitto_passwd.1 \
|
||||
mosquitto_pub.1 \
|
||||
mosquitto_rr.1 \
|
||||
@ -29,6 +30,7 @@ install :
|
||||
$(INSTALL) -d "${DESTDIR}$(mandir)/man5"
|
||||
$(INSTALL) -m 644 mosquitto.conf.5 "${DESTDIR}${mandir}/man5/mosquitto.conf.5"
|
||||
$(INSTALL) -d "${DESTDIR}$(mandir)/man1"
|
||||
$(INSTALL) -m 644 mosquitto_ctrl.1 "${DESTDIR}${mandir}/man1/mosquitto_ctrl.1"
|
||||
$(INSTALL) -m 644 mosquitto_passwd.1 "${DESTDIR}${mandir}/man1/mosquitto_passwd.1"
|
||||
$(INSTALL) -m 644 mosquitto_pub.1 "${DESTDIR}${mandir}/man1/mosquitto_pub.1"
|
||||
$(INSTALL) -m 644 mosquitto_sub.1 "${DESTDIR}${mandir}/man1/mosquitto_sub.1"
|
||||
@ -42,6 +44,7 @@ install :
|
||||
uninstall :
|
||||
-rm -f "${DESTDIR}${mandir}/man8/mosquitto.8"
|
||||
-rm -f "${DESTDIR}${mandir}/man5/mosquitto.conf.5"
|
||||
-rm -f "${DESTDIR}${mandir}/man1/mosquitto_ctrl.1"
|
||||
-rm -f "${DESTDIR}${mandir}/man1/mosquitto_passwd.1"
|
||||
-rm -f "${DESTDIR}${mandir}/man1/mosquitto_pub.1"
|
||||
-rm -f "${DESTDIR}${mandir}/man1/mosquitto_sub.1"
|
||||
@ -56,6 +59,9 @@ mosquitto.8 : mosquitto.8.xml manpage.xsl
|
||||
mosquitto.conf.5 : mosquitto.conf.5.xml manpage.xsl
|
||||
$(XSLTPROC) $<
|
||||
|
||||
mosquitto_ctrl.1 : mosquitto_ctrl.1.xml manpage.xsl
|
||||
$(XSLTPROC) $<
|
||||
|
||||
mosquitto_passwd.1 : mosquitto_passwd.1.xml manpage.xsl
|
||||
$(XSLTPROC) $<
|
||||
|
||||
@ -87,6 +93,7 @@ html : *.xml
|
||||
potgen :
|
||||
xml2po -o po/mosquitto/mosquitto.8.pot mosquitto.8.xml
|
||||
xml2po -o po/mosquitto.conf/mosquitto.conf.5.pot mosquitto.conf.5.xml
|
||||
xml2po -o po/mosquitto_ctrl/mosquitto_ctrl.1.pot mosquitto_ctrl.1.xml
|
||||
xml2po -o po/mosquitto_passwd/mosquitto_passwd.1.pot mosquitto_passwd.1.xml
|
||||
xml2po -o po/mosquitto_pub/mosquitto_pub.1.pot mosquitto_pub.1.xml
|
||||
xml2po -o po/mosquitto_sub/mosquitto_sub.1.pot mosquitto_sub.1.xml
|
||||
|
@ -511,6 +511,12 @@
|
||||
<manvolnum>5</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto_ctrl-1.html">mosquitto_ctrl</link></refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto_passwd-1.html">mosquitto_passwd</link></refentrytitle>
|
||||
|
5
man/mosquitto_ctrl.1.meta
Normal file
5
man/mosquitto_ctrl.1.meta
Normal file
@ -0,0 +1,5 @@
|
||||
.. title: mosquitto_ctrl man page
|
||||
.. slug: mosquitto_ctrl-1
|
||||
.. category: man
|
||||
.. type: man
|
||||
.. pretty_url: False
|
638
man/mosquitto_ctrl.1.xml
Normal file
638
man/mosquitto_ctrl.1.xml
Normal file
@ -0,0 +1,638 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml-stylesheet type="text/xsl" href="manpage.xsl"?>
|
||||
|
||||
<refentry xml:id="mosquitto_ctrl" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<refmeta>
|
||||
<refentrytitle>mosquitto_ctrl</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
<refmiscinfo class="source">Mosquitto Project</refmiscinfo>
|
||||
<refmiscinfo class="manual">Commands</refmiscinfo>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>mosquitto_ctrl</refname>
|
||||
<refpurpose>a tool for initialising/configuring a Mosquitto broker instance</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>mosquitto_ctrl</command>
|
||||
<arg choice='opt'>connection-options</arg>
|
||||
<arg choice='plain'>module</arg>
|
||||
<arg choice='plain'>module-command</arg>
|
||||
<arg choice='opt'>command-options</arg>
|
||||
</cmdsynopsis>
|
||||
<cmdsynopsis>
|
||||
<command>mosquitto_ctrl</command>
|
||||
<group choice='req'>
|
||||
<arg choice='plain'>
|
||||
<arg><option>-h</option> <replaceable>hostname</replaceable></arg>
|
||||
<arg><option>--unix</option> <replaceable>socket path</replaceable></arg>
|
||||
<arg><option>-p</option> <replaceable>port-number</replaceable></arg>
|
||||
<arg><option>-u</option> <replaceable>username</replaceable></arg>
|
||||
<arg><option>-P</option> <replaceable>password</replaceable></arg>
|
||||
</arg>
|
||||
<arg choice='plain'><option>-L</option> <replaceable>URL</replaceable></arg>
|
||||
</group>
|
||||
<arg><option>-A</option> <replaceable>bind-address</replaceable></arg>
|
||||
<arg><option>-c</option></arg>
|
||||
<arg><option>-d</option></arg>
|
||||
<arg><option>-i</option> <replaceable>client-id</replaceable></arg>
|
||||
<arg><option>-q</option> <replaceable>message-QoS</replaceable></arg>
|
||||
<arg><option>--quiet</option></arg>
|
||||
<arg><option>-V</option> <replaceable>protocol-version</replaceable></arg>
|
||||
<group>
|
||||
<arg>
|
||||
<group choice='req'>
|
||||
<arg choice='plain'><option>--cafile</option> <replaceable>file</replaceable></arg>
|
||||
<arg choice='plain'><option>--capath</option> <replaceable>dir</replaceable></arg>
|
||||
</group>
|
||||
<arg><option>--cert</option> <replaceable>file</replaceable></arg>
|
||||
<arg><option>--key</option> <replaceable>file</replaceable></arg>
|
||||
<arg><option>--ciphers</option> <replaceable>ciphers</replaceable></arg>
|
||||
<arg><option>--tls-version</option> <replaceable>version</replaceable></arg>
|
||||
<arg><option>--tls-alpn</option> <replaceable>protocol</replaceable></arg>
|
||||
<arg><option>--tls-engine</option> <replaceable>engine</replaceable></arg>
|
||||
<arg><option>--keyform</option>
|
||||
<group choice='req'>
|
||||
<arg choice='plain'><replaceable>pem</replaceable></arg>
|
||||
<arg choice='plain'><replaceable>engine</replaceable></arg>
|
||||
</group></arg>
|
||||
<arg><option>--tls-engine-kpass-sha1</option> <replaceable>kpass-sha1</replaceable></arg>
|
||||
<arg><option>--insecure</option></arg>
|
||||
</arg>
|
||||
<arg>
|
||||
<arg choice='plain'><option>--psk</option> <replaceable>hex-key</replaceable></arg>
|
||||
<arg choice='plain'><option>--psk-identity</option> <replaceable>identity</replaceable></arg>
|
||||
<arg><option>--ciphers</option> <replaceable>ciphers</replaceable></arg>
|
||||
<arg><option>--tls-version</option> <replaceable>version</replaceable></arg>
|
||||
</arg>
|
||||
</group>
|
||||
<arg><option>--proxy</option> <replaceable>socks-url</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
<cmdsynopsis>
|
||||
<command>mosquitto_ctrl</command>
|
||||
<group choice='plain'>
|
||||
<arg><option>--help</option></arg>
|
||||
</group>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
<para><command>mosquitto_ctrl</command> is a simple MQTT version 5/3.1.1
|
||||
client that will publish a single message on a topic and
|
||||
exit.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Encrypted Connections</title>
|
||||
<para><command>mosquitto_ctrl</command> supports TLS encrypted
|
||||
connections. It is strongly recommended that you use an encrypted
|
||||
connection for all remote use of mosquitto_ctrl.</para>
|
||||
<para>To enable TLS connections when using x509 certificates, one of
|
||||
either <option>--cafile</option> or <option>--capath</option> must
|
||||
be provided as an option.</para>
|
||||
<para>To enable TLS connections when using TLS-PSK, you must use the
|
||||
<option>--psk</option> and the <option>--psk-identity</option>
|
||||
options.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Dynamic Security Module</title>
|
||||
<para>The dynamic security plugin is an optional authentication and
|
||||
access control plugin for Mosquitto that allows on the fly
|
||||
control of users, groups, and roles. The mosquitto_ctrl
|
||||
<command>dynsec</command> module allows you to interact with
|
||||
a broker using the dynamic security plugin.</para>
|
||||
|
||||
<refsect2>
|
||||
<title>Configuration file initialisation</title>
|
||||
<para></para>
|
||||
<itemizedlist mark="circle">
|
||||
<listitem><para>mosquitto_ctrl dynsec init <literal>filename</literal> <literal>admin-user</literal> admin-password [admin-role]</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Connection Options</title>
|
||||
<para>The options below may be given on the command line, but may also
|
||||
be placed in a config file located at
|
||||
<option>$XDG_CONFIG_HOME/mosquitto_ctrl</option> or
|
||||
<option>$HOME/.config/mosquitto_ctrl</option> with one pair of
|
||||
<option>-option <replaceable>value</replaceable></option>
|
||||
per line. The values in the config file will be used as defaults
|
||||
and can be overridden by using the command line. The exceptions to
|
||||
this are the message type options, of which only one can be
|
||||
specified. Note also that currently some options cannot be negated,
|
||||
e.g. <option>-S</option>. Config file lines that have a
|
||||
<option>#</option> as the first character are treated as comments
|
||||
and not processed any further.</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>-A</option></term>
|
||||
<listitem>
|
||||
<para>Bind the outgoing connection to a local ip
|
||||
address/hostname. Use this argument if you need to
|
||||
restrict network communication to a particular
|
||||
interface.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--cafile</option></term>
|
||||
<listitem>
|
||||
<para>Define the path to a file containing PEM encoded CA
|
||||
certificates that are trusted. Used to enable SSL
|
||||
communication.</para>
|
||||
<para>See also <option>--capath</option></para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--capath</option></term>
|
||||
<listitem>
|
||||
<para>Define the path to a directory containing PEM encoded CA
|
||||
certificates that are trusted. Used to enable SSL
|
||||
communication.</para>
|
||||
<para>For <option>--capath</option> to work correctly, the
|
||||
certificate files must have ".crt" as the file ending
|
||||
and you must run "openssl rehash <path to capath>" each
|
||||
time you add/remove a certificate.</para>
|
||||
<para>See also <option>--cafile</option></para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--cert</option></term>
|
||||
<listitem>
|
||||
<para>Define the path to a file containing a PEM encoded
|
||||
certificate for this client, if required by the
|
||||
server.</para>
|
||||
<para>See also <option>--key</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--ciphers</option></term>
|
||||
<listitem>
|
||||
<para>An openssl compatible list of TLS ciphers to support
|
||||
in the client. See
|
||||
<citerefentry><refentrytitle>ciphers</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
for more information.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-d</option></term>
|
||||
<term><option>--debug</option></term>
|
||||
<listitem>
|
||||
<para>Enable debug messages.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-D</option></term>
|
||||
<term><option>--property</option></term>
|
||||
<listitem>
|
||||
<para>Use an MQTT v5 property with this publish. If you use
|
||||
this option, the client will be set to be an MQTT v5
|
||||
client. This option has two forms:</para>
|
||||
<para><option>-D command identifier value</option></para>
|
||||
<para><option>-D command identifier name value</option></para>
|
||||
<para><option>command</option> is the MQTT command/packet
|
||||
identifier and can be one of CONNECT, PUBLISH, PUBREL,
|
||||
DISCONNECT, AUTH, or WILL. The properties available for
|
||||
each command are listed in the
|
||||
<link linkend='properties'>Properties</link>
|
||||
section.</para>
|
||||
|
||||
<para><option>identifier</option> is the name of the
|
||||
property to add. This is as described in the
|
||||
specification, but with '-' as a word separator. For
|
||||
example:
|
||||
<option>payload-format-indicator</option>. More details
|
||||
are in the <link linkend='properties'>Properties</link>
|
||||
section.</para>
|
||||
|
||||
<para><option>value</option> is the value of the property
|
||||
to add, with a data type that is property
|
||||
specific.</para>
|
||||
|
||||
<para><option>name</option> is only used for the
|
||||
<option>user-property</option> property as the first of
|
||||
the two strings in the string pair. In that case,
|
||||
<option>value</option> is the second of the strings in
|
||||
the pair.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--help</option></term>
|
||||
<listitem>
|
||||
<para>Display usage information.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-h</option></term>
|
||||
<term><option>--host</option></term>
|
||||
<listitem>
|
||||
<para>Specify the host to connect to. Defaults to localhost.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-i</option></term>
|
||||
<term><option>--id</option></term>
|
||||
<listitem>
|
||||
<para>The id to use for this client. If not given, a client id will
|
||||
be generated depending on the MQTT version being used. For v3.1.1/v3.1,
|
||||
the client generates a client id in the format
|
||||
<option>mosq-XXXXXXXXXXXXXXXXXX</option>, where the
|
||||
<option>X</option> are replaced with random alphanumeric
|
||||
characters. For v5.0, the client sends a zero length client id, and the
|
||||
server will generate a client id for the client.</para>
|
||||
|
||||
<para>This option cannot be used at the same time as the
|
||||
<option>--id-prefix</option> argument.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--insecure</option></term>
|
||||
<listitem>
|
||||
<para>When using certificate based encryption, this option
|
||||
disables verification of the server hostname in the
|
||||
server certificate. This can be useful when testing
|
||||
initial server configurations but makes it possible for
|
||||
a malicious third party to impersonate your server
|
||||
through DNS spoofing, for example. Use this option in
|
||||
testing <emphasis>only</emphasis>. If you need to
|
||||
resort to using this option in a production
|
||||
environment, your setup is at fault and there is no
|
||||
point using encryption.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--key</option></term>
|
||||
<listitem>
|
||||
<para>Define the path to a file containing a PEM encoded
|
||||
private key for this client, if required by the
|
||||
server.</para>
|
||||
<para>See also <option>--cert</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--keyform</option></term>
|
||||
<listitem>
|
||||
<para>Specifies the type of private key in use when making
|
||||
TLS connections.. This can be "pem" or "engine". This
|
||||
parameter is useful when a TPM module is being used and
|
||||
the private key has been created with it. Defaults to
|
||||
"pem", which means normal private key files are
|
||||
used.</para>
|
||||
<para>See also <option>--tls-engine</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-L</option></term>
|
||||
<term><option>--url</option></term>
|
||||
<listitem>
|
||||
<para>Specify specify user, password, hostname, port and
|
||||
topic at once as a URL. The URL must be in the form:
|
||||
mqtt(s)://[username[:password]@]host[:port]/topic</para>
|
||||
<para>If the scheme is mqtt:// then the port defaults to
|
||||
1883. If the scheme is mqtts:// then the port defaults
|
||||
to 8883.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--nodelay</option></term>
|
||||
<listitem>
|
||||
<para>Disable Nagle's algorithm for the socket. This means
|
||||
that latency of sent messages is reduced, which is
|
||||
particularly noticable for small, reasonably infrequent
|
||||
messages. Using this option may result in more packets
|
||||
being sent than would normally be necessary.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-p</option></term>
|
||||
<term><option>--port</option></term>
|
||||
<listitem>
|
||||
<para>Connect to the port specified. If not given, the
|
||||
default of 1883 for plain MQTT or 8883 for MQTT over
|
||||
TLS will be used.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-P</option></term>
|
||||
<term><option>--pw</option></term>
|
||||
<listitem>
|
||||
<para>Provide a password to be used for authenticating with
|
||||
the broker. Using this argument without also specifying
|
||||
a username is invalid when using MQTT v3.1 or v3.1.1.
|
||||
See also the <option>--username</option> option.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--proxy</option></term>
|
||||
<listitem>
|
||||
<para>Specify a SOCKS5 proxy to connect through. "None" and
|
||||
"username" authentication types are supported. The
|
||||
<option>socks-url</option> must be of the form
|
||||
<option>socks5h://[username[:password]@]host[:port]</option>.
|
||||
The protocol prefix <option>socks5h</option> means that
|
||||
hostnames are resolved by the proxy. The symbols %25,
|
||||
%3A and %40 are URL decoded into %, : and @
|
||||
respectively, if present in the username or
|
||||
password.</para>
|
||||
<para>If username is not given, then no authentication is
|
||||
attempted. If the port is not given, then the default
|
||||
of 1080 is used.</para>
|
||||
<para>More SOCKS versions may be available in the future,
|
||||
depending on demand, and will use different protocol
|
||||
prefixes as described in <citerefentry>
|
||||
<refentrytitle>curl</refentrytitle>
|
||||
<manvolnum>1</manvolnum> </citerefentry>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--psk</option></term>
|
||||
<listitem>
|
||||
<para>Provide the hexadecimal (no leading 0x)
|
||||
pre-shared-key matching the one used on the broker to
|
||||
use TLS-PSK encryption support.
|
||||
<option>--psk-identity</option> must also be provided
|
||||
to enable TLS-PSK.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--psk-identity</option></term>
|
||||
<listitem>
|
||||
<para>The client identity to use with TLS-PSK support. This
|
||||
may be used instead of a username if the broker is
|
||||
configured to do so.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-q</option></term>
|
||||
<term><option>--qos</option></term>
|
||||
<listitem>
|
||||
<para>Specify the quality of service to use for messages, from 0, 1 and 2. Defaults to 1.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--quiet</option></term>
|
||||
<listitem>
|
||||
<para>If this argument is given, no runtime errors will be
|
||||
printed. This excludes any error messages given in case of
|
||||
invalid user input (e.g. using <option>--port</option> without a
|
||||
port).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tls-alpn</option></term>
|
||||
<listitem>
|
||||
<para>Provide a protocol to use when connecting to a broker
|
||||
that has multiple protocols available on a single port,
|
||||
e.g. MQTT and WebSockets.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tls-engine</option></term>
|
||||
<listitem>
|
||||
<para>A valid openssl engine id. These can be listed with
|
||||
openssl engine command.</para>
|
||||
<para>See also <option>--keyform</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tls-engine-kpass-sha1</option></term>
|
||||
<listitem>
|
||||
<para>SHA1 of the private key password when using an TLS
|
||||
engine. Some TLS engines such as the TPM engine may
|
||||
require the use of a password in order to be accessed.
|
||||
This option allows a hex encoded SHA1 hash of the
|
||||
password to the engine directly, instead of the user
|
||||
being prompted for the password.</para>
|
||||
<para>See also <option>--tls-engine</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tls-version</option></term>
|
||||
<listitem>
|
||||
<para>Choose which TLS protocol version to use when
|
||||
communicating with the broker. Valid options are
|
||||
<option>tlsv1.3</option>, <option>tlsv1.2</option> and
|
||||
<option>tlsv1.1</option>. The default value is
|
||||
<option>tlsv1.2</option>. Must match the protocol
|
||||
version used by the broker.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-u</option></term>
|
||||
<term><option>--username</option></term>
|
||||
<listitem>
|
||||
<para>Provide a username to be used for authenticating with
|
||||
the broker. See also the <option>--pw</option>
|
||||
argument.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unix</option></term>
|
||||
<listitem>
|
||||
<para>Connect to a broker through a local unix domain socket
|
||||
instead of a TCP socket. This is a replacement for
|
||||
<option>-h</option> and <option>-L</option>. For example:
|
||||
<option>mosquitto_ctrl --unix /tmp/mosquitto.sock ...</option>
|
||||
</para>
|
||||
<para>See the <option>socket_domain</option> option in
|
||||
<refentrytitle>
|
||||
<link xlink:href="mosquitto-conf-5.html">mosquitto.conf</link>
|
||||
</refentrytitle><manvolnum>5</manvolnum>
|
||||
to configure Mosquitto to listen on a unix socket.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>-V</option></term>
|
||||
<term><option>--protocol-version</option></term>
|
||||
<listitem>
|
||||
<para>Specify which version of the MQTT protocol should be
|
||||
used when connecting to the rmeote broker. Can be
|
||||
<option>5</option>, <option>311</option>,
|
||||
<option>31</option>, or the more verbose
|
||||
<option>mqttv5</option>, <option>mqttv311</option>, or
|
||||
<option>mqttv31</option>.
|
||||
Defaults to <option>311</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id='properties'>
|
||||
<title>Properties</title>
|
||||
<para>The <option>-D</option> / <option>--property</option> option
|
||||
allows adding properties to different stages of the mosquitto_ctrl
|
||||
run. The properties supported for each command are as
|
||||
follows:</para>
|
||||
|
||||
<refsect2>
|
||||
<title>Connect</title>
|
||||
<itemizedlist>
|
||||
<listitem><para><option>authentication-data</option> (binary data - note treated as a string in mosquitto_ctrl)</para></listitem>
|
||||
<listitem><para><option>authentication-method</option> (UTF-8 string pair)</para></listitem>
|
||||
<listitem><para><option>maximum-packet-size</option> (32-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>receive-maximum</option> (16-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>request-problem-information</option> (8-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>request-response-information</option> (8-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>session-expiry-interval</option> (32-bit unsigned integer, note use <option>-x</option> instead)</para></listitem>
|
||||
<listitem><para><option>topic-alias-maximum</option> (16-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>user-property</option> (UTF-8 string pair)</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Publish</title>
|
||||
<itemizedlist>
|
||||
<listitem><para><option>content-type</option> (UTF-8 string)</para></listitem>
|
||||
<listitem><para><option>correlation-data</option> (binary data - note treated as a string in mosquitto_ctrl)</para></listitem>
|
||||
<listitem><para><option>message-expiry-interval</option> (32-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>payload-format-indicator</option> (8-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>response-topic</option> (UTF-8 string)</para></listitem>
|
||||
<listitem><para><option>topic-alias</option> (16-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>user-property</option> (UTF-8 string pair)</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Disconnect</title>
|
||||
<itemizedlist>
|
||||
<listitem><para><option>session-expiry-interval</option> (32-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>user-property</option> (UTF-8 string pair)</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Will properties</title>
|
||||
<itemizedlist>
|
||||
<listitem><para><option>content-type</option> (UTF-8 string)</para></listitem>
|
||||
<listitem><para><option>correlation-data</option> (binary data - note treated as a string in mosquitto_ctrl)</para></listitem>
|
||||
<listitem><para><option>message-expiry-interval</option> (32-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>payload-format-indicator</option> (8-bit unsigned integer)</para></listitem>
|
||||
<listitem><para><option>response-topic</option> (UTF-8 string)</para></listitem>
|
||||
<listitem><para><option>user-property</option> (UTF-8 string pair)</para></listitem>
|
||||
<listitem><para><option>will-delay-interval</option> (32-bit unsigned integer)</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit Status</title>
|
||||
<para>
|
||||
mosquitto_sub returns zero on success, or non-zero on error. If
|
||||
the connection is refused by the broker at the MQTT level, then
|
||||
the exit code is the CONNACK reason code. If another error
|
||||
occurs, the exit code is a libmosquitto return value.
|
||||
</para>
|
||||
|
||||
<para>MQTT v3.1.1 CONNACK codes:</para>
|
||||
<itemizedlist mark="circle">
|
||||
<listitem><para><option>0</option> Success</para></listitem>
|
||||
<listitem><para><option>1</option> Connection refused: Bad protocol version</para></listitem>
|
||||
<listitem><para><option>2</option> Connection refused: Identifier rejected</para></listitem>
|
||||
<listitem><para><option>3</option> Connection refused: Server unavailable</para></listitem>
|
||||
<listitem><para><option>4</option> Connection refused: Bad username/password</para></listitem>
|
||||
<listitem><para><option>5</option> Connection refused: Not authorized</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>MQTT v5 CONNACK codes:</para>
|
||||
<itemizedlist>
|
||||
<listitem><para><option>0</option> Success</para></listitem>
|
||||
<listitem><para><option>128</option> Unspecified error</para></listitem>
|
||||
<listitem><para><option>129</option> Malformed packet</para></listitem>
|
||||
<listitem><para><option>130</option> Protocol error</para></listitem>
|
||||
<listitem><para><option>131</option> Implementation specific error</para></listitem>
|
||||
<listitem><para><option>132</option> Unsupported protocol version</para></listitem>
|
||||
<listitem><para><option>133</option> Client ID not valid</para></listitem>
|
||||
<listitem><para><option>134</option> Bad username or password</para></listitem>
|
||||
<listitem><para><option>135</option> Not authorized</para></listitem>
|
||||
<listitem><para><option>136</option> Server unavailable</para></listitem>
|
||||
<listitem><para><option>137</option> Server busy</para></listitem>
|
||||
<listitem><para><option>138</option> Banned</para></listitem>
|
||||
<listitem><para><option>139</option> Server shutting down</para></listitem>
|
||||
<listitem><para><option>140</option> Bad authentication method</para></listitem>
|
||||
<listitem><para><option>141</option> Keep alive timeout</para></listitem>
|
||||
<listitem><para><option>142</option> Session taken over</para></listitem>
|
||||
<listitem><para><option>143</option> Topic filter invalid</para></listitem>
|
||||
<listitem><para><option>144</option> Topic name invalid</para></listitem>
|
||||
<listitem><para><option>147</option> Receive maximum exceeded</para></listitem>
|
||||
<listitem><para><option>148</option> Topic alias invalid</para></listitem>
|
||||
<listitem><para><option>149</option> Packet too large</para></listitem>
|
||||
<listitem><para><option>148</option> Message rate too high</para></listitem>
|
||||
<listitem><para><option>151</option> Quota exceeded</para></listitem>
|
||||
<listitem><para><option>152</option> Administrative action</para></listitem>
|
||||
<listitem><para><option>153</option> Payload format invalid</para></listitem>
|
||||
<listitem><para><option>154</option> Retain not supported</para></listitem>
|
||||
<listitem><para><option>155</option> QoS not supported</para></listitem>
|
||||
<listitem><para><option>156</option> Use another server</para></listitem>
|
||||
<listitem><para><option>157</option> Server moved</para></listitem>
|
||||
<listitem><para><option>158</option> Shared subscriptions not supported</para></listitem>
|
||||
<listitem><para><option>159</option> Connection rate exceeded</para></listitem>
|
||||
<listitem><para><option>160</option> Maximum connect time</para></listitem>
|
||||
<listitem><para><option>161</option> Subscription IDs not supported</para></listitem>
|
||||
<listitem><para><option>162</option> Wildcard subscriptions not supported</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Bugs</title>
|
||||
<para><command>mosquitto</command> bug information can be found at
|
||||
<ulink url="https://github.com/eclipse/mosquitto/issues"/></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<simplelist type="inline">
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mqtt-7.html">mqtt</link></refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto_rr-1.html">mosquitto_rr</link></refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto_pub-1.html">mosquitto_pub</link></refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto_sub-1.html">mosquitto_sub</link></refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto-8.html">mosquitto</link></refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="libmosquitto-3.html">libmosquitto</link></refentrytitle>
|
||||
<manvolnum>3</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
<member>
|
||||
<citerefentry>
|
||||
<refentrytitle><link xlink:href="mosquitto-tls-7.html">mosquitto-tls</link></refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
</citerefentry>
|
||||
</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Author</title>
|
||||
<para>Roger Light <email>roger@atchoo.org</email></para>
|
||||
</refsect1>
|
||||
</refentry>
|
@ -1,2 +1,3 @@
|
||||
add_subdirectory(dynamic-security)
|
||||
add_subdirectory(message-timestamp)
|
||||
add_subdirectory(payload-modification)
|
||||
|
@ -1,4 +1,5 @@
|
||||
DIRS= \
|
||||
dynamic-security \
|
||||
message-timestamp \
|
||||
payload-modification
|
||||
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
This directory contains plugins for use with Mosquitto.
|
||||
|
||||
## Dynamic security
|
||||
This is a fully functioning plugin that implements authentication and access
|
||||
control, with configuration via a $CONTROL topic. See the readme in
|
||||
dynamic-security for more information.
|
||||
|
||||
## Message timestamp
|
||||
This is an **example** plugin to demonstrate how it is possible to attach MQTT v5 properties to messages after they have been received, and before they are sent on to subscribers.
|
||||
|
||||
|
36
plugins/dynamic-security/CMakeLists.txt
Normal file
36
plugins/dynamic-security/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
set( CLIENT_INC ${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/include
|
||||
${STDBOOL_H_PATH} ${STDINT_H_PATH} ${PTHREAD_INCLUDE_DIR}
|
||||
${OPENSSL_INCLUDE_DIR})
|
||||
|
||||
set( CLIENT_DIR ${mosquitto_BINARY_DIR}/lib)
|
||||
|
||||
if (CJSON_FOUND)
|
||||
set( CLIENT_DIR "${CLIENT_DIR} ${CJSON_DIR}" )
|
||||
set( CLIENT_INC "${CLIENT_INC};${CJSON_INCLUDE_DIRS}" )
|
||||
endif()
|
||||
|
||||
include_directories(${CLIENT_INC})
|
||||
link_directories(${CLIENT_DIR})
|
||||
|
||||
add_library(mosquitto_dynamic_security SHARED
|
||||
acl.c
|
||||
auth.c
|
||||
clients.c
|
||||
dynamic_security.h
|
||||
groups.c
|
||||
json_help.c
|
||||
json_help.h
|
||||
plugin.c
|
||||
roles.c
|
||||
sub_matches_sub.c)
|
||||
|
||||
set_target_properties(mosquitto_dynamic_security PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE 1
|
||||
)
|
||||
set_target_properties(mosquitto_dynamic_security PROPERTIES PREFIX "")
|
||||
|
||||
if (CJSON_FOUND)
|
||||
target_link_libraries(mosquitto_dynamic_security ${CJSON_LIBRARIES})
|
||||
endif()
|
||||
|
||||
install(TARGETS mosquitto_dynamic_security RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
60
plugins/dynamic-security/Makefile
Normal file
60
plugins/dynamic-security/Makefile
Normal file
@ -0,0 +1,60 @@
|
||||
include ../../config.mk
|
||||
|
||||
.PHONY : all binary check clean reallyclean test install uninstall
|
||||
|
||||
PLUGIN_NAME=mosquitto_dynamic_security
|
||||
LOCAL_CPPFLAGS=-I/usr/include/cjson -I/usr/local/include/cjson -I../../src/
|
||||
|
||||
OBJS= \
|
||||
acl.o \
|
||||
auth.o \
|
||||
clients.o \
|
||||
groups.o \
|
||||
json_help.o \
|
||||
plugin.o \
|
||||
roles.o \
|
||||
sub_matches_sub.o
|
||||
|
||||
all : binary
|
||||
binary : ${PLUGIN_NAME}.so
|
||||
|
||||
${PLUGIN_NAME}.so : ${OBJS}
|
||||
${CROSS_COMPILE}${CC} $(PLUGIN_LDFLAGS) -fPIC -shared $^ -o $@ -lcjson
|
||||
|
||||
acl.o : acl.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
auth.o : auth.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
clients.o : clients.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
groups.o : groups.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
json_help.o : json_help.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
plugin.o : plugin.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
roles.o : roles.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
sub_matches_sub.o : sub_matches_sub.c dynamic_security.h
|
||||
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
|
||||
|
||||
reallyclean : clean
|
||||
clean:
|
||||
-rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno
|
||||
|
||||
check: test
|
||||
test:
|
||||
|
||||
install: ${PLUGIN_NAME}.so
|
||||
$(INSTALL) -d "${DESTDIR}$(prefix)/lib"
|
||||
$(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${prefix}/lib/${PLUGIN_NAME}.so"
|
||||
|
||||
uninstall :
|
||||
-rm -f "${DESTDIR}${prefix}/lib/${PLUGIN_NAME}.so"
|
579
plugins/dynamic-security/README.md
Normal file
579
plugins/dynamic-security/README.md
Normal file
@ -0,0 +1,579 @@
|
||||
# Mosquitto Dynamic Security
|
||||
|
||||
This document describes a topic based mechanism for controlling security in
|
||||
Mosquitto. JSON commands are published to topics like `$CONTROL/<feature>/v1`
|
||||
|
||||
## Clients
|
||||
|
||||
When a client connects to Mosquitto, it can optionally provide a username. The
|
||||
username maps the client instance to a client on the broker, if it exists.
|
||||
Multiple clients can make use of the same username, and hence the same broker
|
||||
client.
|
||||
|
||||
## Groups
|
||||
|
||||
Broker clients can be defined as belonging to zero or more broker groups.
|
||||
|
||||
## Roles
|
||||
|
||||
Roles can be applied to a client or a group, and define what that client/group
|
||||
is allowed to do, for example what topics it may or may not publish or
|
||||
subscribe to.
|
||||
|
||||
## Commands
|
||||
|
||||
### Set default ACL access
|
||||
|
||||
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.
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "setDefaultACLAccess",
|
||||
"acls":[
|
||||
{ "acltype": "publishClientToBroker", "allow": false },
|
||||
{ "acltype": "publishBrokerToClient", "allow": false },
|
||||
{ "acltype": "subscribe", "allow": false },
|
||||
{ "acltype": "unsubscribe", "allow": false }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec setDefaultACLAccess subscribe deny
|
||||
```
|
||||
|
||||
## Create Client
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "createClient",
|
||||
"username": "new username",
|
||||
"password": "new password",
|
||||
"clientid": "", # Optional
|
||||
"textname": "", # Optional
|
||||
"textdescription": "", # Optional
|
||||
"groups": [
|
||||
{ "groupname": "group", "priority": 1 }
|
||||
], # Optional, groups must exist
|
||||
"roles": [
|
||||
{ "rolename": "role", "priority": -1 }
|
||||
] # Optional, roles must exist
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec createClient username password
|
||||
```
|
||||
|
||||
## Delete Client
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "deleteClient",
|
||||
"username": "username to delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec deleteClient username
|
||||
```
|
||||
|
||||
## Get Client
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "getClient",
|
||||
"username": "required username"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec getClient username
|
||||
```
|
||||
|
||||
## List Clients
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "listClients",
|
||||
"verbose": false,
|
||||
"count": -1, # -1 for all, or a positive integer for a limited count
|
||||
"offset": 0 # Where in the list to start
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec listClients 10 20
|
||||
```
|
||||
|
||||
## Modify Existing Client
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "modifyClient",
|
||||
"username": "username to modify"
|
||||
"textname": "", # Optional
|
||||
"textdescription": "", # Optional
|
||||
"roles": [
|
||||
{ "rolename": "role", "priority": 1 }
|
||||
], # Optional
|
||||
"groups": [
|
||||
{ "groupname": "group", "priority": 1 }
|
||||
], # Optional
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Modifying clients isn't currently possible with mosquitto_ctrl.
|
||||
|
||||
## Set Client Password
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "setClientPassword",
|
||||
"username": "username to change",
|
||||
"password": "new password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec setClientPassword username password
|
||||
```
|
||||
|
||||
## Add Client Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "addClientRole",
|
||||
"username": "client to add role to",
|
||||
"rolename": "role to add",
|
||||
"priority": -1 # Optional priority
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec addClientRole username rolename
|
||||
```
|
||||
|
||||
## Remove Client Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "removeClientRole",
|
||||
"username": "client to remove role from",
|
||||
"rolename": "role to remove"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec removeClientRole username rolename
|
||||
```
|
||||
|
||||
## Add Client to a Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "addGroupClient",
|
||||
"groupname": "group to add client to",
|
||||
"username": "client to add to group",
|
||||
"priority": -1 # Priority of the group for the client
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec addGroupClient groupname username
|
||||
```
|
||||
|
||||
## Create Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "createGroup",
|
||||
"groupname": "new group",
|
||||
"roles": [
|
||||
{ "rolename": "role", "priority": 1 }
|
||||
] # Optional, roles must exist
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec createGroup groupname
|
||||
```
|
||||
|
||||
## Delete Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "deleteGroup",
|
||||
"groupname: "group to delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec deleteGroup groupname
|
||||
```
|
||||
|
||||
## Get Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "getGroup",
|
||||
"groupname: "group to get"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec getGroup groupname
|
||||
```
|
||||
|
||||
## List Groups
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "listGroups",
|
||||
"verbose": false,
|
||||
"count": -1, # -1 for all, or a positive integer for a limited count
|
||||
"offset": 0 # Where in the list to start
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec listGroups
|
||||
```
|
||||
|
||||
## Modify Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "modifyGroup",
|
||||
"groupname": "group to modify",
|
||||
"textname": "", # Optional
|
||||
"textdescription": "", # Optional
|
||||
"roles": [
|
||||
{ "rolename": "role", "priority": 1 }
|
||||
], # Optional
|
||||
"clients": [
|
||||
{ "username": "client", "priority": 1 }
|
||||
] # Optional
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Modifying groups isn't currently possible with mosquitto_ctrl.
|
||||
|
||||
## Remove Client from a Group
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "removeGroupClient",
|
||||
"groupname": "group to remove client from",
|
||||
"username": "client to remove from group"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec removeGroupClient groupname username
|
||||
```
|
||||
|
||||
## Add Group Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "addGroupRole",
|
||||
"groupname": "group to add role to",
|
||||
"rolename": "role to add",
|
||||
"priority": -1 # Optional priority
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec addGroupRole groupname rolename
|
||||
```
|
||||
|
||||
## Remove Group Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "removeGroupRole",
|
||||
"groupname": "group",
|
||||
"rolename": "role"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec removeGroupRole groupname rolename
|
||||
```
|
||||
|
||||
## Set Group for Anonymous Clients
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "setAnonymousGroup",
|
||||
"groupname": "group"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec setAnonymousGroup groupname
|
||||
```
|
||||
|
||||
## Create Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "createRole",
|
||||
"rolename": "new role",
|
||||
"textname": "", # Optional
|
||||
"textdescription": "", # Optional
|
||||
"acls": [
|
||||
{ "acltype": "subscribePattern", "topic": "topic/#", "priority": -1, "allow": true}
|
||||
] # Optional
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec createRole rolename
|
||||
```
|
||||
|
||||
## Get Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "getRole",
|
||||
"rolename": "role",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec getRole rolename
|
||||
```
|
||||
|
||||
## List Roles
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "listRoles",
|
||||
"verbose": false,
|
||||
"count": -1, # -1 for all, or a positive integer for a limited count
|
||||
"offset": 0 # Where in the list to start
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec listRoles
|
||||
```
|
||||
|
||||
## Modify Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "modifyRole",
|
||||
"rolename": "role to modify"
|
||||
"textname": "", # Optional
|
||||
"textdescription": "", # Optional
|
||||
"acls": [
|
||||
{ "acltype": "subscribePattern", "topic": "topic/#", "priority": -1, "allow": true }
|
||||
] # Optional
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Modifying roles isn't currently possible with mosquitto_ctrl.
|
||||
|
||||
## Delete Role
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "deleteRole",
|
||||
"rolename": "role"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec deleteRole rolename
|
||||
```
|
||||
|
||||
## Add Role ACL
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "addRoleACL",
|
||||
"acltype": "subscribePattern",
|
||||
"topic": "topic/#",
|
||||
"priority": -1,
|
||||
"allow": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec addRoleACL rolename subscribeLiteral topic/# deny
|
||||
```
|
||||
|
||||
## Remove Role ACL
|
||||
|
||||
Command:
|
||||
```
|
||||
{
|
||||
"commands":[
|
||||
{
|
||||
"command": "removeRoleACL",
|
||||
"acltype": "subscribePattern",
|
||||
"topic": "topic/#"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mosquitto_ctrl example:
|
||||
```
|
||||
mosquitto_ctrl dynsec removeRoleACL rolename subscribeLiteral topic/#
|
||||
```
|
248
plugins/dynamic-security/acl.c
Normal file
248
plugins/dynamic-security/acl.c
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 "dynamic_security.h"
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_broker.h"
|
||||
#include "mosquitto_plugin.h"
|
||||
|
||||
typedef int (*MOSQ_FUNC_acl_check)(struct mosquitto_evt_acl_check *, struct dynsec__rolelist *);
|
||||
|
||||
/* FIXME - CACHE! */
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - publish broker to client
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int acl_check_publish_b2c(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
struct dynsec__acl *acl, *acl_tmp;
|
||||
bool result;
|
||||
|
||||
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
|
||||
HASH_ITER(hh, rolelist->role->acls.publish_b2c, acl, acl_tmp){
|
||||
mosquitto_topic_matches_sub(acl->topic, ed->topic, &result);
|
||||
if(result){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - publish client to broker
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int acl_check_publish_c2b(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
struct dynsec__acl *acl, *acl_tmp;
|
||||
bool result;
|
||||
|
||||
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
|
||||
HASH_ITER(hh, rolelist->role->acls.publish_c2b, acl, acl_tmp){
|
||||
mosquitto_topic_matches_sub(acl->topic, ed->topic, &result);
|
||||
if(result){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - subscribe
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int acl_check_subscribe(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
struct dynsec__acl *acl, *acl_tmp;
|
||||
int len;
|
||||
|
||||
len = strlen(ed->topic);
|
||||
|
||||
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
|
||||
HASH_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, acl);
|
||||
if(acl){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
HASH_ITER(hh, rolelist->role->acls.subscribe_pattern, acl, acl_tmp){
|
||||
if(sub_acl_check(acl->topic, ed->topic)){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - unsubscribe
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int acl_check_unsubscribe(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
struct dynsec__acl *acl, *acl_tmp;
|
||||
int len;
|
||||
|
||||
len = strlen(ed->topic);
|
||||
|
||||
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
|
||||
HASH_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, acl);
|
||||
if(acl){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
HASH_ITER(hh, rolelist->role->acls.unsubscribe_pattern, acl, acl_tmp){
|
||||
if(sub_acl_check(acl->topic, ed->topic)){
|
||||
if(acl->allow){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_ACL_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - generic check
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int acl_check(struct mosquitto_evt_acl_check *ed, MOSQ_FUNC_acl_check check, bool default_access)
|
||||
{
|
||||
struct dynsec__client *client;
|
||||
struct dynsec__grouplist *grouplist, *grouplist_tmp;
|
||||
const char *username;
|
||||
int rc;
|
||||
|
||||
username = mosquitto_client_username(ed->client);
|
||||
|
||||
if(username){
|
||||
client = dynsec_clients__find(username);
|
||||
if(client == NULL) return MOSQ_ERR_PLUGIN_DEFER;
|
||||
|
||||
/* Client roles */
|
||||
rc = check(ed, client->rolelist);
|
||||
if(rc != MOSQ_ERR_NOT_FOUND){
|
||||
return rc;
|
||||
}
|
||||
|
||||
HASH_ITER(hh, client->grouplist, grouplist, grouplist_tmp){
|
||||
rc = check(ed, grouplist->group->rolelist);
|
||||
if(rc != MOSQ_ERR_NOT_FOUND){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}else if(dynsec_anonymous_group){
|
||||
/* If we have a group for anonymous users, use that for checking. */
|
||||
rc = check(ed, dynsec_anonymous_group->rolelist);
|
||||
if(rc != MOSQ_ERR_NOT_FOUND){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
if(default_access == false){
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}else{
|
||||
if(!strncmp(ed->topic, "$CONTROL", strlen("$CONTROL"))){
|
||||
/* We never give fall through access to $CONTROL topics, they must
|
||||
* be granted explicitly. */
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}else{
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL check - plugin callback
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec__acl_check_callback(int event, void *event_data, void *userdata)
|
||||
{
|
||||
struct mosquitto_evt_acl_check *ed = event_data;
|
||||
|
||||
/* ACL checks are made in the order below until a match occurs, at which
|
||||
* point the decision is made.
|
||||
*
|
||||
* User roles in priority order highest to lowest.
|
||||
* Roles have their ACLs checked in priority order, highest to lowest
|
||||
* Groups are processed in priority order highest to lowest
|
||||
* Group roles are processed in priority order, highest to lowest
|
||||
* Roles have their ACLs checked in priority order, highest to lowest
|
||||
*/
|
||||
|
||||
switch(ed->access){
|
||||
case MOSQ_ACL_SUBSCRIBE:
|
||||
return acl_check(event_data, acl_check_subscribe, default_access.subscribe);
|
||||
break;
|
||||
case MOSQ_ACL_UNSUBSCRIBE:
|
||||
return acl_check(event_data, acl_check_unsubscribe, default_access.unsubscribe);
|
||||
break;
|
||||
case MOSQ_ACL_WRITE: /* Client to broker */
|
||||
return acl_check(event_data, acl_check_publish_c2b, default_access.publish_c2b);
|
||||
break;
|
||||
case MOSQ_ACL_READ:
|
||||
return acl_check(event_data, acl_check_publish_b2c, default_access.publish_b2c);
|
||||
break;
|
||||
default:
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}
|
193
plugins/dynamic-security/auth.c
Normal file
193
plugins/dynamic-security/auth.c
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "dynamic_security.h"
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_broker.h"
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Base64 encoding/decoding
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec_auth__base64_encode(unsigned char *in, unsigned int in_len, char **encoded)
|
||||
{
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_write(b64, in, in_len);
|
||||
if(BIO_flush(b64) != 1){
|
||||
BIO_free_all(b64);
|
||||
return 1;
|
||||
}
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
*encoded = mosquitto_malloc(bptr->length+1);
|
||||
if(!(*encoded)){
|
||||
BIO_free_all(b64);
|
||||
return 1;
|
||||
}
|
||||
memcpy(*encoded, bptr->data, bptr->length);
|
||||
(*encoded)[bptr->length] = '\0';
|
||||
BIO_free_all(b64);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_auth__base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len)
|
||||
{
|
||||
BIO *bmem, *b64;
|
||||
int slen;
|
||||
|
||||
slen = strlen(in);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
if(!b64){
|
||||
return 1;
|
||||
}
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
if(!bmem){
|
||||
BIO_free_all(b64);
|
||||
return 1;
|
||||
}
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_write(bmem, in, slen);
|
||||
|
||||
if(BIO_flush(bmem) != 1){
|
||||
BIO_free_all(b64);
|
||||
return 1;
|
||||
}
|
||||
*decoded = mosquitto_calloc(slen, 1);
|
||||
if(!(*decoded)){
|
||||
BIO_free_all(b64);
|
||||
return 1;
|
||||
}
|
||||
*decoded_len = BIO_read(b64, *decoded, slen);
|
||||
BIO_free_all(b64);
|
||||
|
||||
if(*decoded_len <= 0){
|
||||
mosquitto_free(*decoded);
|
||||
*decoded = NULL;
|
||||
*decoded_len = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Password functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password)
|
||||
{
|
||||
const EVP_MD *digest;
|
||||
int iterations;
|
||||
|
||||
if(new_password){
|
||||
if(RAND_bytes(client->pw.salt, sizeof(client->pw.salt)) != 1){
|
||||
return MOSQ_ERR_UNKNOWN;
|
||||
}
|
||||
iterations = PW_DEFAULT_ITERATIONS;
|
||||
}else{
|
||||
iterations = client->pw.iterations;
|
||||
}
|
||||
if(iterations < 1){
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
client->pw.iterations = iterations;
|
||||
|
||||
digest = EVP_get_digestbyname("sha512");
|
||||
if(!digest){
|
||||
return MOSQ_ERR_UNKNOWN;
|
||||
}
|
||||
|
||||
return !PKCS5_PBKDF2_HMAC(password, strlen(password),
|
||||
client->pw.salt, sizeof(client->pw.salt), iterations,
|
||||
digest, password_hash_len, password_hash);
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Username/password check
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int memcmp_const(const void *a, const void *b, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
int rc = 0;
|
||||
|
||||
if(!a || !b) return 1;
|
||||
|
||||
for(i=0; i<len; i++){
|
||||
if( ((char *)a)[i] != ((char *)b)[i] ){
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata)
|
||||
{
|
||||
struct mosquitto_evt_basic_auth *ed = event_data;
|
||||
struct dynsec__client *client;
|
||||
unsigned char password_hash[64]; /* For SHA512 */
|
||||
const char *clientid;
|
||||
|
||||
if(ed->username == NULL || ed->password == NULL) return MOSQ_ERR_PLUGIN_DEFER;
|
||||
|
||||
client = dynsec_clients__find(ed->username);
|
||||
if(client){
|
||||
if(client->clientid){
|
||||
clientid = mosquitto_client_id(ed->client);
|
||||
if(clientid == NULL || strcmp(client->clientid, clientid)){
|
||||
return MOSQ_ERR_AUTH;
|
||||
}
|
||||
}
|
||||
if(dynsec_auth__pw_hash(client, ed->password, password_hash, sizeof(password_hash), false) == MOSQ_ERR_SUCCESS){
|
||||
if(memcmp_const(client->pw.password_hash, password_hash, sizeof(password_hash)) == 0){
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_AUTH;
|
||||
}
|
||||
}else{
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}
|
||||
}else{
|
||||
return MOSQ_ERR_PLUGIN_DEFER;
|
||||
}
|
||||
}
|
868
plugins/dynamic-security/clients.c
Normal file
868
plugins/dynamic-security/clients.c
Normal file
@ -0,0 +1,868 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <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_rolelists__free_all(&client->rolelist);
|
||||
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;
|
||||
struct dynsec__client *client;
|
||||
struct dynsec__role *role;
|
||||
unsigned char *buf;
|
||||
unsigned int buf_len;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Hash iterations */
|
||||
jtmp = cJSON_GetObjectItem(j_client, "iterations");
|
||||
if(jtmp == NULL || !cJSON_IsNumber(jtmp)){
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}
|
||||
iterations = jtmp->valuedouble;
|
||||
if(iterations < 1){
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}else{
|
||||
client->pw.iterations = iterations;
|
||||
}
|
||||
|
||||
/* Salt */
|
||||
jtmp = cJSON_GetObjectItem(j_client, "salt");
|
||||
if(jtmp == NULL || !cJSON_IsString(jtmp)){
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}
|
||||
if(dynsec_auth__base64_decode(jtmp->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS
|
||||
|| buf_len != sizeof(client->pw.salt)){
|
||||
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}
|
||||
memcpy(client->pw.salt, buf, buf_len);
|
||||
mosquitto_free(buf);
|
||||
|
||||
/* Password */
|
||||
jtmp = cJSON_GetObjectItem(j_client, "password");
|
||||
if(jtmp == NULL || !cJSON_IsString(jtmp)){
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}
|
||||
if(dynsec_auth__base64_decode(jtmp->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS
|
||||
|| buf_len != sizeof(client->pw.password_hash)){
|
||||
|
||||
// FIXME log
|
||||
mosquitto_free(client->username);
|
||||
mosquitto_free(client);
|
||||
continue;
|
||||
}
|
||||
memcpy(client->pw.password_hash, buf, buf_len);
|
||||
mosquitto_free(buf);
|
||||
|
||||
/* 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_rolelists__add_role(&client->rolelist, role, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
char s_iterations[10];
|
||||
|
||||
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)
|
||||
){
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
j_roles = dynsec_rolelists__all_to_json(client->rolelist);
|
||||
if(j_roles == NULL){
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(j_client, "roles", j_roles);
|
||||
|
||||
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);
|
||||
|
||||
if(dynsec_auth__base64_encode(client->pw.salt, sizeof(client->pw.salt), &buf) != MOSQ_ERR_SUCCESS){
|
||||
return 1;
|
||||
}
|
||||
|
||||
jtmp = cJSON_CreateString(buf);
|
||||
mosquitto_free(buf);
|
||||
if(jtmp == NULL) return 1;
|
||||
cJSON_AddItemToObject(j_client, "salt", jtmp);
|
||||
|
||||
snprintf(s_iterations, sizeof(s_iterations), "%d", client->pw.iterations);
|
||||
if(cJSON_AddRawToObject(j_client, "iterations", s_iterations) == NULL){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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(json_get_string(command, "password", &password, false) != MOSQ_ERR_SUCCESS){
|
||||
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(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(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;
|
||||
}
|
||||
if(clientid){
|
||||
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_rolelists__load_from_json(command, &client->rolelist);
|
||||
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);
|
||||
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_set_password(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *username, *password;
|
||||
struct dynsec__client *client;
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
client = dynsec_clients__find(username);
|
||||
if(client == NULL){
|
||||
dynsec__command_reply(j_responses, context, "setClientPassword", "Client not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
if(dynsec_auth__pw_hash(client, password, client->pw.password_hash, sizeof(client->pw.password_hash), true) == MOSQ_ERR_SUCCESS){
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "setClientPassword", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "setClientPassword", "Internal error", correlation_data);
|
||||
// FIXME - this should fail safe without modifying the existing password
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *username;
|
||||
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;
|
||||
}
|
||||
|
||||
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, "textname", &text_name, true) == MOSQ_ERR_SUCCESS){
|
||||
str = mosquitto_strdup(text_name);
|
||||
if(str == NULL){
|
||||
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
mosquitto_free(client->text_name);
|
||||
client->text_name = str;
|
||||
}
|
||||
|
||||
if(json_get_string(command, "textdescription", &text_description, true) == MOSQ_ERR_SUCCESS){
|
||||
str = mosquitto_strdup(text_description);
|
||||
if(str == NULL){
|
||||
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
mosquitto_free(client->text_description);
|
||||
client->text_description = str;
|
||||
}
|
||||
|
||||
rc = dynsec_rolelists__load_from_json(command, &rolelist);
|
||||
if(rc == MOSQ_ERR_SUCCESS){
|
||||
dynsec_rolelists__free_all(&client->rolelist);
|
||||
client->rolelist = rolelist;
|
||||
}else if(rc == MOSQ_ERR_NOT_FOUND){
|
||||
dynsec__command_reply(j_responses, context, "modifyClient", "Role not found", correlation_data);
|
||||
dynsec_rolelists__free_all(&rolelist);
|
||||
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_rolelists__free_all(&rolelist);
|
||||
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);
|
||||
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){
|
||||
dynsec_groups__remove_client(username, grouplist->groupname, false);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|| (j_groups = cJSON_AddArrayToObject(j_client, "groups")) == NULL
|
||||
){
|
||||
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j_roles = dynsec_rolelists__all_to_json(client->rolelist);
|
||||
if(j_roles == NULL){
|
||||
cJSON_Delete(j_client);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToObject(j_client, "roles", j_roles);
|
||||
|
||||
j_groups = dynsec_grouplists__all_to_json(client->grouplist);
|
||||
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;
|
||||
}
|
||||
|
||||
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_CreateString(correlation_data);
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "getClient", "Internal error", correlation_data);
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "correlationData", jtmp);
|
||||
}
|
||||
|
||||
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;
|
||||
char buf[30];
|
||||
|
||||
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);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", HASH_CNT(hh, local_clients));
|
||||
cJSON_AddRawToObject(j_data, "totalCount", buf);
|
||||
|
||||
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_CreateString(correlation_data);
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "listClients", "Internal error", correlation_data);
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "correlationData", jtmp);
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(j_responses, tree);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void dynsec_clients__remove_role_from_all(const struct dynsec__role *role)
|
||||
{
|
||||
struct dynsec__client *client, *client_tmp;
|
||||
|
||||
HASH_ITER(hh, local_clients, client, client_tmp){
|
||||
dynsec_rolelists__remove_role(&client->rolelist, role);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *username, *role_name;
|
||||
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(json_get_string(command, "rolename", &role_name, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "addClientRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
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(role_name);
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "addClientRole", "Role not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
dynsec_rolelists__add_role(&client->rolelist, role, priority);
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "addClientRole", NULL, correlation_data);
|
||||
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(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;
|
||||
}
|
||||
|
||||
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_rolelists__remove_role(&client->rolelist, role);
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "removeClientRole", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
223
plugins/dynamic-security/dynamic_security.h
Normal file
223
plugins/dynamic-security/dynamic_security.h
Normal file
@ -0,0 +1,223 @@
|
||||
#ifndef DYNAMIC_SECURITY_H
|
||||
#define DYNAMIC_SECURITY_H
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <uthash.h>
|
||||
#include "mosquitto.h"
|
||||
#include "password_mosq.h"
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Error codes
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
#define ERR_USER_NOT_FOUND 10000
|
||||
#define ERR_GROUP_NOT_FOUND 10001
|
||||
#define ERR_LIST_NOT_FOUND 10002
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Datatypes
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
struct dynsec__clientlist{
|
||||
UT_hash_handle hh;
|
||||
char *username;
|
||||
struct dynsec__client *client;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct dynsec__grouplist{
|
||||
UT_hash_handle hh;
|
||||
char *groupname;
|
||||
struct dynsec__group *group;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct dynsec__rolelist{
|
||||
UT_hash_handle hh;
|
||||
char *rolename;
|
||||
struct dynsec__role *role;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct dynsec__client{
|
||||
UT_hash_handle hh;
|
||||
struct mosquitto_pw pw;
|
||||
struct dynsec__rolelist *rolelist;
|
||||
struct dynsec__grouplist *grouplist;
|
||||
char *username;
|
||||
char *clientid;
|
||||
char *text_name;
|
||||
char *text_description;
|
||||
};
|
||||
|
||||
struct dynsec__group{
|
||||
UT_hash_handle hh;
|
||||
struct dynsec__rolelist *rolelist;
|
||||
struct dynsec__clientlist *clientlist;
|
||||
char *groupname;
|
||||
char *text_name;
|
||||
char *text_description;
|
||||
};
|
||||
|
||||
|
||||
struct dynsec__acl{
|
||||
UT_hash_handle hh;
|
||||
char *topic;
|
||||
int priority;
|
||||
bool allow;
|
||||
};
|
||||
|
||||
struct dynsec__acls{
|
||||
struct dynsec__acl *publish_c2b;
|
||||
struct dynsec__acl *publish_b2c;
|
||||
struct dynsec__acl *subscribe_literal;
|
||||
struct dynsec__acl *subscribe_pattern;
|
||||
struct dynsec__acl *unsubscribe_literal;
|
||||
struct dynsec__acl *unsubscribe_pattern;
|
||||
};
|
||||
|
||||
struct dynsec__role{
|
||||
UT_hash_handle hh;
|
||||
struct dynsec__acls acls;
|
||||
char *rolename;
|
||||
char *text_name;
|
||||
char *text_description;
|
||||
};
|
||||
|
||||
struct dynsec__acl_default_access{
|
||||
bool publish_c2b;
|
||||
bool publish_b2c;
|
||||
bool subscribe;
|
||||
bool unsubscribe;
|
||||
};
|
||||
|
||||
extern struct dynsec__group *dynsec_anonymous_group;
|
||||
extern struct dynsec__acl_default_access default_access;
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Plugin Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
void dynsec__config_save(void);
|
||||
int dynsec__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON *commands);
|
||||
void dynsec__command_reply(cJSON *j_responses, struct mosquitto *context, const char *command, const char *error, const char *correlation_data);
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # ACL Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec__acl_check_callback(int event, void *event_data, void *userdata);
|
||||
bool sub_acl_check(const char *acl, const char *sub);
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Auth Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec_auth__base64_encode(unsigned char *in, unsigned int in_len, char **encoded);
|
||||
int dynsec_auth__base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len);
|
||||
int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password);
|
||||
int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata);
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Client Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
void dynsec_clients__cleanup(void);
|
||||
int dynsec_clients__config_load(cJSON *tree);
|
||||
int dynsec_clients__config_save(cJSON *tree);
|
||||
int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_clients__process_set_password(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
struct dynsec__client *dynsec_clients__find(const char *username);
|
||||
void dynsec_clients__remove_role_from_all(const struct dynsec__role *role);
|
||||
|
||||
cJSON *dynsec_clientlists__all_to_json(struct dynsec__clientlist *base_clientlist);
|
||||
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Group Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
void dynsec_groups__cleanup(void);
|
||||
int dynsec_groups__config_load(cJSON *tree);
|
||||
int dynsec_groups__add_client(const char *username, const char *groupname, int priority, bool update_config);
|
||||
int dynsec_groups__config_save(cJSON *tree);
|
||||
int dynsec_groups__process_add_client(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_groups__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_groups__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_groups__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_groups__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_groups__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
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_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);
|
||||
void dynsec_groups__remove_role_from_all(const struct dynsec__role *role);
|
||||
|
||||
cJSON *dynsec_grouplists__all_to_json(struct dynsec__grouplist *base_grouplist);
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Role Functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
void dynsec_roles__cleanup(void);
|
||||
int dynsec_roles__config_load(cJSON *tree);
|
||||
int dynsec_roles__config_save(cJSON *tree);
|
||||
int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
|
||||
struct dynsec__role *dynsec_roles__find(const char *rolename);
|
||||
|
||||
int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority);
|
||||
int dynsec_rolelists__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist);
|
||||
int dynsec_rolelists__remove_role(struct dynsec__rolelist **base_rolelist, const struct dynsec__role *role);
|
||||
void dynsec_rolelists__free_all(struct dynsec__rolelist **base_rolelist);
|
||||
cJSON *dynsec_rolelists__all_to_json(struct dynsec__rolelist *base_rolelist);
|
||||
|
||||
#endif
|
1023
plugins/dynamic-security/groups.c
Normal file
1023
plugins/dynamic-security/groups.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,8 +18,9 @@ Contributors:
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mosquitto_broker_internal.h"
|
||||
#include "mosquitto.h"
|
||||
|
||||
|
||||
int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value)
|
||||
@ -30,7 +31,7 @@ int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, boo
|
||||
*value = default_value;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
|
||||
jtmp = cJSON_GetObjectItem(json, name);
|
||||
if(jtmp){
|
||||
if(cJSON_IsBool(jtmp) == false){
|
||||
return MOSQ_ERR_INVAL;
|
||||
@ -53,7 +54,7 @@ int json_get_int(cJSON *json, const char *name, int *value, bool optional, int d
|
||||
*value = default_value;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
|
||||
jtmp = cJSON_GetObjectItem(json, name);
|
||||
if(jtmp){
|
||||
if(cJSON_IsNumber(jtmp) == false){
|
||||
return MOSQ_ERR_INVAL;
|
||||
@ -74,7 +75,7 @@ int json_get_string(cJSON *json, const char *name, char **value, bool optional)
|
||||
|
||||
*value = NULL;
|
||||
|
||||
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
|
||||
jtmp = cJSON_GetObjectItem(json, name);
|
||||
if(jtmp){
|
||||
if(cJSON_IsString(jtmp) == false){
|
||||
return MOSQ_ERR_INVAL;
|
485
plugins/dynamic-security/plugin.c
Normal file
485
plugins/dynamic-security/plugin.c
Normal file
@ -0,0 +1,485 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "json_help.h"
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_broker.h"
|
||||
#include "mosquitto_plugin.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;
|
||||
|
||||
payload = cJSON_PrintUnformatted(tree);
|
||||
cJSON_Delete(tree);
|
||||
if(payload == NULL) return;
|
||||
|
||||
mosquitto_broker_publish(NULL, "$CONTROL/dynamic-security/v1/response",
|
||||
strlen(payload), 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_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, "publishClientToBroker")){
|
||||
default_access.publish_c2b = allow;
|
||||
}else if(!strcasecmp(j_acltype->valuestring, "publishBrokerToClient")){
|
||||
default_access.publish_b2c = allow;
|
||||
}else if(!strcasecmp(j_acltype->valuestring, "subscribe")){
|
||||
default_access.subscribe = allow;
|
||||
}else if(!strcasecmp(j_acltype->valuestring, "unsubscribe")){
|
||||
default_access.unsubscribe = allow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "setDefaultACLAccess", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0; i<supported_version_count; i++){
|
||||
if(supported_versions[i] == 5){
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int dynsec__general_config_load(cJSON *tree)
|
||||
{
|
||||
cJSON *j_default_access, *jtmp;
|
||||
|
||||
j_default_access = cJSON_GetObjectItem(tree, "defaultACLAccess");
|
||||
if(j_default_access && cJSON_IsObject(j_default_access)){
|
||||
jtmp = cJSON_GetObjectItem(j_default_access, "publishClientToBroker");
|
||||
if(jtmp && cJSON_IsBool(jtmp)){
|
||||
default_access.publish_c2b = cJSON_IsTrue(jtmp);
|
||||
}else{
|
||||
default_access.publish_c2b = false;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(j_default_access, "publishBrokerToClient");
|
||||
if(jtmp && cJSON_IsBool(jtmp)){
|
||||
default_access.publish_b2c = cJSON_IsTrue(jtmp);
|
||||
}else{
|
||||
default_access.publish_b2c = false;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(j_default_access, "subscribe");
|
||||
if(jtmp && cJSON_IsBool(jtmp)){
|
||||
default_access.subscribe = cJSON_IsTrue(jtmp);
|
||||
}else{
|
||||
default_access.subscribe = false;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(j_default_access, "unsubscribe");
|
||||
if(jtmp && cJSON_IsBool(jtmp)){
|
||||
default_access.unsubscribe = cJSON_IsTrue(jtmp);
|
||||
}else{
|
||||
default_access.unsubscribe = false;
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
static int dynsec__general_config_save(cJSON *tree)
|
||||
{
|
||||
cJSON *j_default_access;
|
||||
|
||||
j_default_access = cJSON_CreateObject();
|
||||
if(j_default_access == NULL){
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "defaultACLAccess", j_default_access);
|
||||
|
||||
if(cJSON_AddBoolToObject(j_default_access, "publishClientToBroker", default_access.publish_c2b) == NULL
|
||||
|| cJSON_AddBoolToObject(j_default_access, "publishBrokerToClient", default_access.publish_b2c) == NULL
|
||||
|| cJSON_AddBoolToObject(j_default_access, "subscribe", default_access.subscribe) == NULL
|
||||
|| cJSON_AddBoolToObject(j_default_access, "unsubscribe", default_access.unsubscribe) == NULL
|
||||
){
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
static int dynsec__config_load(void)
|
||||
{
|
||||
FILE *fptr;
|
||||
long flen;
|
||||
char *json_str;
|
||||
cJSON *tree;
|
||||
|
||||
/* Save to file */
|
||||
fptr = fopen(config_file, "rt");
|
||||
if(fptr == NULL){
|
||||
return 1;
|
||||
}
|
||||
|
||||
fseek(fptr, 0, SEEK_END);
|
||||
flen = ftell(fptr);
|
||||
fseek(fptr, 0, SEEK_SET);
|
||||
json_str = mosquitto_calloc(flen+1, sizeof(char));
|
||||
if(json_str == NULL){
|
||||
fclose(fptr);
|
||||
return 1;
|
||||
}
|
||||
if(fread(json_str, 1, flen, fptr) != flen){
|
||||
fclose(fptr);
|
||||
return 1;
|
||||
}
|
||||
fclose(fptr);
|
||||
|
||||
tree = cJSON_Parse(json_str);
|
||||
mosquitto_free(json_str);
|
||||
if(tree == NULL){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(dynsec__general_config_load(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(dynsec_roles__config_load(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(dynsec_clients__config_load(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(dynsec_groups__config_load(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON_Delete(tree);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void dynsec__config_save(void)
|
||||
{
|
||||
cJSON *tree;
|
||||
int file_path_len;
|
||||
char *file_path;
|
||||
FILE *fptr;
|
||||
int json_str_len;
|
||||
char *json_str;
|
||||
|
||||
tree = cJSON_CreateObject();
|
||||
if(tree == NULL) return;
|
||||
|
||||
if(dynsec__general_config_save(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return;
|
||||
}
|
||||
|
||||
if(dynsec_clients__config_save(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return;
|
||||
}
|
||||
|
||||
if(dynsec_groups__config_save(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return;
|
||||
}
|
||||
|
||||
if(dynsec_roles__config_save(tree)){
|
||||
cJSON_Delete(tree);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Print json to string */
|
||||
json_str = cJSON_Print(tree);
|
||||
if(json_str == NULL){
|
||||
cJSON_Delete(tree);
|
||||
return;
|
||||
}
|
||||
cJSON_Delete(tree);
|
||||
json_str_len = strlen(json_str);
|
||||
|
||||
/* Save to file */
|
||||
file_path_len = strlen(config_file) + 1;
|
||||
file_path = mosquitto_malloc(file_path_len);
|
||||
if(file_path == NULL){
|
||||
mosquitto_free(json_str);
|
||||
return;
|
||||
}
|
||||
snprintf(file_path, file_path_len, "%s.new", config_file);
|
||||
|
||||
fptr = fopen(file_path, "wt");
|
||||
if(fptr == NULL){
|
||||
mosquitto_free(json_str);
|
||||
mosquitto_free(file_path);
|
||||
return;
|
||||
}
|
||||
fwrite(json_str, 1, json_str_len, fptr);
|
||||
mosquitto_free(json_str);
|
||||
fclose(fptr);
|
||||
|
||||
/* Everything is ok, so move new file over proper file */
|
||||
rename(file_path, config_file);
|
||||
mosquitto_free(file_path);
|
||||
}
|
||||
|
||||
|
||||
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *options, int option_count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0; i<option_count; i++){
|
||||
if(!strcasecmp(options[i].key, "config_file")){
|
||||
config_file = mosquitto_strdup(options[i].value);
|
||||
if(config_file == NULL){
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(config_file == NULL){
|
||||
mosquitto_log_printf(MOSQ_LOG_WARNING, "Warning: Dynamic security plugin has no plugin_opt_config_file defined. The plugin will not be activated.");
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
plg_id = identifier;
|
||||
|
||||
dynsec__config_load();
|
||||
mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", NULL);
|
||||
mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, NULL);
|
||||
mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, NULL);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *options, int option_count)
|
||||
{
|
||||
if(plg_id){
|
||||
mosquitto_callback_unregister(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1");
|
||||
mosquitto_callback_unregister(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL);
|
||||
mosquitto_callback_unregister(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL);
|
||||
}
|
||||
dynsec_groups__cleanup();
|
||||
dynsec_clients__cleanup();
|
||||
dynsec_roles__cleanup();
|
||||
|
||||
mosquitto_free(config_file);
|
||||
config_file = NULL;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # $CONTROL/dynamic-security/v1 handler
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
int dynsec__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){
|
||||
dynsec__command_reply(j_responses, context, command, "Invalid correlationData data type.", NULL);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
/* Plugin */
|
||||
if(!strcasecmp(command, "setDefaultACLAccess")){
|
||||
rc = dynsec__process_default_acl_access(j_responses, context, aiter, correlation_data);
|
||||
|
||||
/* Clients */
|
||||
}else if(!strcasecmp(command, "createClient")){
|
||||
rc = dynsec_clients__process_create(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "deleteClient")){
|
||||
rc = dynsec_clients__process_delete(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "getClient")){
|
||||
rc = dynsec_clients__process_get(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "listClients")){
|
||||
rc = dynsec_clients__process_list(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "modifyClient")){
|
||||
rc = dynsec_clients__process_modify(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "setClientPassword")){
|
||||
rc = dynsec_clients__process_set_password(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "addClientRole")){
|
||||
rc = dynsec_clients__process_add_role(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "removeClientRole")){
|
||||
rc = dynsec_clients__process_remove_role(j_responses, context, aiter, correlation_data);
|
||||
|
||||
/* Groups */
|
||||
}else if(!strcasecmp(command, "addGroupClient")){
|
||||
rc = dynsec_groups__process_add_client(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "createGroup")){
|
||||
rc = dynsec_groups__process_create(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "deleteGroup")){
|
||||
rc = dynsec_groups__process_delete(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "getGroup")){
|
||||
rc = dynsec_groups__process_get(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "listGroups")){
|
||||
rc = dynsec_groups__process_list(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "modifyGroup")){
|
||||
rc = dynsec_groups__process_modify(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "removeGroupClient")){
|
||||
rc = dynsec_groups__process_remove_client(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "addGroupRole")){
|
||||
rc = dynsec_groups__process_add_role(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "removeGroupRole")){
|
||||
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);
|
||||
|
||||
/* Roles */
|
||||
}else if(!strcasecmp(command, "createRole")){
|
||||
rc = dynsec_roles__process_create(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "getRole")){
|
||||
rc = dynsec_roles__process_get(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "listRoles")){
|
||||
rc = dynsec_roles__process_list(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "modifyRole")){
|
||||
rc = dynsec_roles__process_modify(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "deleteRole")){
|
||||
rc = dynsec_roles__process_delete(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "addRoleACL")){
|
||||
rc = dynsec_roles__process_add_acl(j_responses, context, aiter, correlation_data);
|
||||
}else if(!strcasecmp(command, "removeRoleACL")){
|
||||
rc = dynsec_roles__process_remove_acl(j_responses, context, aiter, correlation_data);
|
||||
|
||||
/* Unknown */
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, command, "Unknown command", correlation_data);
|
||||
rc = MOSQ_ERR_INVAL;
|
||||
}
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "Unknown command", "Missing command", correlation_data);
|
||||
rc = MOSQ_ERR_INVAL;
|
||||
}
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "Unknown command", "Command not an object", correlation_data);
|
||||
rc = MOSQ_ERR_INVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
953
plugins/dynamic-security/roles.c
Normal file
953
plugins/dynamic-security/roles.c
Normal file
@ -0,0 +1,953 @@
|
||||
/*
|
||||
Copyright (c) 2020 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <cJSON.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <uthash.h>
|
||||
#include <utlist.h>
|
||||
|
||||
#include "dynamic_security.h"
|
||||
#include "json_help.h"
|
||||
#include "mosquitto.h"
|
||||
#include "mosquitto_broker.h"
|
||||
|
||||
|
||||
static cJSON *add_role_to_json(struct dynsec__role *role, bool verbose);
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Local variables
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static struct dynsec__role *local_roles = NULL;
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Utility functions
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
static int role_cmp(void *a, void *b)
|
||||
{
|
||||
struct dynsec__role *role_a = a;
|
||||
struct dynsec__role *role_b = b;
|
||||
|
||||
return strcmp(role_a->rolename, role_b->rolename);
|
||||
}
|
||||
|
||||
static int rolelist_cmp(void *a, void *b)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist_a = a;
|
||||
struct dynsec__rolelist *rolelist_b = b;
|
||||
|
||||
return rolelist_b->priority - rolelist_a->priority;
|
||||
}
|
||||
|
||||
|
||||
void dynsec_rolelists__free_item(struct dynsec__rolelist **base_rolelist, struct dynsec__rolelist *rolelist)
|
||||
{
|
||||
HASH_DELETE(hh, *base_rolelist, rolelist);
|
||||
mosquitto_free(rolelist->rolename);
|
||||
mosquitto_free(rolelist);
|
||||
}
|
||||
|
||||
void dynsec_rolelists__free_all(struct dynsec__rolelist **base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
|
||||
HASH_ITER(hh, *base_rolelist, rolelist, rolelist_tmp){
|
||||
dynsec_rolelists__free_item(base_rolelist, rolelist);
|
||||
}
|
||||
}
|
||||
|
||||
int dynsec_rolelists__remove_role(struct dynsec__rolelist **base_rolelist, const struct dynsec__role *role)
|
||||
{
|
||||
struct dynsec__rolelist *found_rolelist;
|
||||
|
||||
HASH_FIND(hh, *base_rolelist, role->rolename, strlen(role->rolename), found_rolelist);
|
||||
if(found_rolelist){
|
||||
dynsec_rolelists__free_item(base_rolelist, found_rolelist);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist;
|
||||
|
||||
if(role == NULL) return MOSQ_ERR_INVAL;
|
||||
|
||||
HASH_FIND(hh, *base_rolelist, role->rolename, strlen(role->rolename), rolelist);
|
||||
if(rolelist){
|
||||
return MOSQ_ERR_ALREADY_EXISTS;
|
||||
}else{
|
||||
rolelist = mosquitto_calloc(1, sizeof(struct dynsec__rolelist));
|
||||
if(rolelist == NULL) return MOSQ_ERR_NOMEM;
|
||||
|
||||
rolelist->role = role;
|
||||
rolelist->priority = priority;
|
||||
rolelist->rolename = mosquitto_strdup(role->rolename);
|
||||
if(rolelist->rolename == NULL){
|
||||
mosquitto_free(rolelist);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
HASH_ADD_KEYPTR_INORDER(hh, *base_rolelist, role->rolename, strlen(role->rolename), rolelist, rolelist_cmp);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int dynsec_rolelists__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist)
|
||||
{
|
||||
cJSON *j_roles, *j_role, *j_rolename;
|
||||
int priority;
|
||||
struct dynsec__role *role;
|
||||
|
||||
j_roles = cJSON_GetObjectItem(command, "roles");
|
||||
if(j_roles && cJSON_IsArray(j_roles)){
|
||||
cJSON_ArrayForEach(j_role, j_roles){
|
||||
j_rolename = cJSON_GetObjectItem(j_role, "rolename");
|
||||
if(j_rolename && cJSON_IsString(j_rolename)){
|
||||
json_get_int(j_role, "priority", &priority, true, -1);
|
||||
role = dynsec_roles__find(j_rolename->valuestring);
|
||||
if(role){
|
||||
dynsec_rolelists__add_role(rolelist, role, priority);
|
||||
}else{
|
||||
dynsec_rolelists__free_all(rolelist);
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
return ERR_LIST_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cJSON *dynsec_rolelists__all_to_json(struct dynsec__rolelist *base_rolelist)
|
||||
{
|
||||
struct dynsec__rolelist *rolelist, *rolelist_tmp;
|
||||
cJSON *j_roles, *j_role;
|
||||
char buf[30];
|
||||
|
||||
j_roles = cJSON_CreateArray();
|
||||
if(j_roles == NULL) return NULL;
|
||||
|
||||
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
|
||||
j_role = cJSON_CreateObject();
|
||||
if(j_role == NULL){
|
||||
cJSON_Delete(j_roles);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(j_roles, j_role);
|
||||
|
||||
if(rolelist->priority != -1){
|
||||
snprintf(buf, sizeof(buf), "%d", rolelist->priority);
|
||||
}
|
||||
if(cJSON_AddStringToObject(j_role, "rolename", rolelist->role->rolename) == NULL
|
||||
|| (rolelist->priority != -1 && cJSON_AddRawToObject(j_role, "priority", buf) == NULL)
|
||||
){
|
||||
|
||||
cJSON_Delete(j_roles);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return j_roles;
|
||||
}
|
||||
|
||||
|
||||
static void role__free_acl(struct dynsec__acl **acl, struct dynsec__acl *item)
|
||||
{
|
||||
HASH_DELETE(hh, *acl, item);
|
||||
mosquitto_free(item->topic);
|
||||
mosquitto_free(item);
|
||||
}
|
||||
|
||||
static void role__free_all_acls(struct dynsec__acl **acl)
|
||||
{
|
||||
struct dynsec__acl *iter, *tmp;
|
||||
|
||||
HASH_ITER(hh, *acl, iter, tmp){
|
||||
role__free_acl(acl, iter);
|
||||
}
|
||||
}
|
||||
|
||||
static void role__free_item(struct dynsec__role *role, bool remove_from_hash)
|
||||
{
|
||||
if(remove_from_hash){
|
||||
HASH_DEL(local_roles, role);
|
||||
}
|
||||
mosquitto_free(role->text_name);
|
||||
mosquitto_free(role->text_description);
|
||||
mosquitto_free(role->rolename);
|
||||
role__free_all_acls(&role->acls.publish_c2b);
|
||||
role__free_all_acls(&role->acls.publish_b2c);
|
||||
role__free_all_acls(&role->acls.subscribe_literal);
|
||||
role__free_all_acls(&role->acls.subscribe_pattern);
|
||||
role__free_all_acls(&role->acls.unsubscribe_literal);
|
||||
role__free_all_acls(&role->acls.unsubscribe_pattern);
|
||||
mosquitto_free(role);
|
||||
}
|
||||
|
||||
struct dynsec__role *dynsec_roles__find(const char *rolename)
|
||||
{
|
||||
struct dynsec__role *role = NULL;
|
||||
|
||||
if(rolename){
|
||||
HASH_FIND(hh, local_roles, rolename, strlen(rolename), role);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
|
||||
void dynsec_roles__cleanup(void)
|
||||
{
|
||||
struct dynsec__role *role, *role_tmp;
|
||||
|
||||
HASH_ITER(hh, local_roles, role, role_tmp){
|
||||
role__free_item(role, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ################################################################
|
||||
* #
|
||||
* # Config file load and save
|
||||
* #
|
||||
* ################################################################ */
|
||||
|
||||
|
||||
static int add_single_acl_to_json(cJSON *j_array, const char *acl_type, struct dynsec__acl *acl)
|
||||
{
|
||||
struct dynsec__acl *iter, *tmp;
|
||||
cJSON *j_acl;
|
||||
|
||||
HASH_ITER(hh, acl, iter, tmp){
|
||||
j_acl = cJSON_CreateObject();
|
||||
if(j_acl == NULL){
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToArray(j_array, j_acl);
|
||||
|
||||
if(cJSON_AddStringToObject(j_acl, "acltype", acl_type) == NULL
|
||||
|| cJSON_AddStringToObject(j_acl, "topic", iter->topic) == NULL
|
||||
|| cJSON_AddNumberToObject(j_acl, "priority", iter->priority) == NULL
|
||||
|| cJSON_AddBoolToObject(j_acl, "allow", iter->allow) == NULL
|
||||
){
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_acls_to_json(cJSON *j_role, struct dynsec__role *role)
|
||||
{
|
||||
cJSON *j_acls;
|
||||
|
||||
if((j_acls = cJSON_AddArrayToObject(j_role, "acls")) == NULL){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(add_single_acl_to_json(j_acls, "publishClientToBroker", role->acls.publish_c2b) != MOSQ_ERR_SUCCESS
|
||||
|| add_single_acl_to_json(j_acls, "publishBrokerToClient", role->acls.publish_b2c) != MOSQ_ERR_SUCCESS
|
||||
|| add_single_acl_to_json(j_acls, "subscribeLiteral", role->acls.subscribe_literal) != MOSQ_ERR_SUCCESS
|
||||
|| add_single_acl_to_json(j_acls, "subscribePattern", role->acls.subscribe_pattern) != MOSQ_ERR_SUCCESS
|
||||
|| add_single_acl_to_json(j_acls, "unsubscribeLiteral", role->acls.unsubscribe_literal) != MOSQ_ERR_SUCCESS
|
||||
|| add_single_acl_to_json(j_acls, "unsubscribePattern", role->acls.unsubscribe_pattern) != MOSQ_ERR_SUCCESS
|
||||
){
|
||||
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dynsec_roles__config_save(cJSON *tree)
|
||||
{
|
||||
cJSON *j_roles, *j_role;
|
||||
struct dynsec__role *role, *role_tmp;
|
||||
|
||||
j_roles = cJSON_CreateArray();
|
||||
if(j_roles == NULL){
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "roles", j_roles);
|
||||
|
||||
HASH_ITER(hh, local_roles, role, role_tmp){
|
||||
j_role = add_role_to_json(role, true);
|
||||
if(j_role == NULL){
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToArray(j_roles, j_role);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int insert_acl_cmp(struct dynsec__acl *a, struct dynsec__acl *b)
|
||||
{
|
||||
return b->priority - a->priority;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec__acl **acllist)
|
||||
{
|
||||
cJSON *j_acl, *j_type, *jtmp;
|
||||
struct dynsec__acl *acl;
|
||||
|
||||
cJSON_ArrayForEach(j_acl, j_acls){
|
||||
j_type = cJSON_GetObjectItem(j_acl, "acltype");
|
||||
if(j_type == NULL || !cJSON_IsString(j_type) || strcasecmp(j_type->valuestring, key) != 0){
|
||||
continue;
|
||||
}
|
||||
acl = mosquitto_calloc(1, sizeof(struct dynsec__acl));
|
||||
if(acl == NULL){
|
||||
return 1;
|
||||
}
|
||||
|
||||
json_get_int(j_acl, "priority", &acl->priority, true, 0);
|
||||
json_get_bool(j_acl, "allow", &acl->allow, true, false);
|
||||
|
||||
jtmp = cJSON_GetObjectItem(j_acl, "allow");
|
||||
if(jtmp && cJSON_IsBool(jtmp)){
|
||||
acl->allow = cJSON_IsTrue(jtmp);
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(j_acl, "topic");
|
||||
if(jtmp && cJSON_IsString(jtmp)){
|
||||
acl->topic = mosquitto_strdup(jtmp->valuestring);
|
||||
}
|
||||
|
||||
if(acl->topic == NULL){
|
||||
mosquitto_free(acl);
|
||||
continue;
|
||||
}
|
||||
|
||||
HASH_ADD_KEYPTR_INORDER(hh, *acllist, acl->topic, strlen(acl->topic), acl, insert_acl_cmp);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__config_load(cJSON *tree)
|
||||
{
|
||||
cJSON *j_roles, *j_role, *jtmp, *j_acls;
|
||||
struct dynsec__role *role;
|
||||
|
||||
j_roles = cJSON_GetObjectItem(tree, "roles");
|
||||
if(j_roles == NULL){
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(cJSON_IsArray(j_roles) == false){
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON_ArrayForEach(j_role, j_roles){
|
||||
if(cJSON_IsObject(j_role) == true){
|
||||
role = mosquitto_calloc(1, sizeof(struct dynsec__role));
|
||||
if(role == NULL){
|
||||
// FIXME log
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
/* Role name */
|
||||
jtmp = cJSON_GetObjectItem(j_role, "rolename");
|
||||
if(jtmp == NULL){
|
||||
// FIXME log
|
||||
mosquitto_free(role);
|
||||
continue;
|
||||
}
|
||||
role->rolename = mosquitto_strdup(jtmp->valuestring);
|
||||
if(role->rolename == NULL){
|
||||
// FIXME log
|
||||
mosquitto_free(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Text name */
|
||||
jtmp = cJSON_GetObjectItem(j_role, "textname");
|
||||
if(jtmp != NULL){
|
||||
role->text_name = mosquitto_strdup(jtmp->valuestring);
|
||||
if(role->text_name == NULL){
|
||||
// FIXME log
|
||||
mosquitto_free(role->rolename);
|
||||
mosquitto_free(role);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Text description */
|
||||
jtmp = cJSON_GetObjectItem(j_role, "textdescription");
|
||||
if(jtmp != NULL){
|
||||
role->text_description = mosquitto_strdup(jtmp->valuestring);
|
||||
if(role->text_description == NULL){
|
||||
// FIXME log
|
||||
mosquitto_free(role->text_name);
|
||||
mosquitto_free(role->rolename);
|
||||
mosquitto_free(role);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* ACLs */
|
||||
j_acls = cJSON_GetObjectItem(j_role, "acls");
|
||||
if(j_acls && cJSON_IsArray(j_acls)){
|
||||
if(dynsec_roles__acl_load(j_acls, "publishClientToBroker", &role->acls.publish_c2b) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "publishBrokerToClient", &role->acls.publish_b2c) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribeLiteral", &role->acls.subscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribePattern", &role->acls.subscribe_pattern) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribeLiteral", &role->acls.unsubscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribePattern", &role->acls.unsubscribe_pattern) != 0
|
||||
){
|
||||
|
||||
// FIXME log
|
||||
mosquitto_free(role->rolename);
|
||||
mosquitto_free(role);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
HASH_ADD_KEYPTR(hh, local_roles, role->rolename, strlen(role->rolename), role);
|
||||
}
|
||||
}
|
||||
HASH_SORT(local_roles, role_cmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
char *text_name, *text_description;
|
||||
struct dynsec__role *role;
|
||||
int rc = MOSQ_ERR_SUCCESS;
|
||||
cJSON *j_acls;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(json_get_string(command, "textname", &text_name, true) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "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, "createRole", "Invalid/missing textdescription", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Role already exists", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
role = mosquitto_calloc(1, sizeof(struct dynsec__role));
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
role->rolename = mosquitto_strdup(rolename);
|
||||
if(role->rolename == NULL){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Internal error", correlation_data);
|
||||
rc = MOSQ_ERR_NOMEM;
|
||||
goto error;
|
||||
}
|
||||
if(text_name){
|
||||
role->text_name = mosquitto_strdup(text_name);
|
||||
if(role->text_name == NULL){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Internal error", correlation_data);
|
||||
rc = MOSQ_ERR_NOMEM;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if(text_description){
|
||||
role->text_description = mosquitto_strdup(text_description);
|
||||
if(role->text_description == NULL){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Internal error", correlation_data);
|
||||
rc = MOSQ_ERR_NOMEM;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* ACLs */
|
||||
j_acls = cJSON_GetObjectItem(command, "acls");
|
||||
if(j_acls && cJSON_IsArray(j_acls)){
|
||||
if(dynsec_roles__acl_load(j_acls, "publishClientToBroker", &role->acls.publish_c2b) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "publishBrokerToClient", &role->acls.publish_b2c) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribeLiteral", &role->acls.subscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribePattern", &role->acls.subscribe_pattern) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribeLiteral", &role->acls.unsubscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribePattern", &role->acls.unsubscribe_pattern) != 0
|
||||
){
|
||||
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Internal error", correlation_data);
|
||||
rc = MOSQ_ERR_NOMEM;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HASH_ADD_KEYPTR_INORDER(hh, local_roles, role->rolename, strlen(role->rolename), role, role_cmp);
|
||||
|
||||
dynsec__config_save();
|
||||
|
||||
dynsec__command_reply(j_responses, context, "createRole", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
error:
|
||||
if(role){
|
||||
role__free_item(role, false);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
struct dynsec__role *role;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "deleteRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role){
|
||||
dynsec_clients__remove_role_from_all(role);
|
||||
dynsec_groups__remove_role_from_all(role);
|
||||
role__free_item(role, true);
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "deleteRole", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "deleteRole", "Role not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static cJSON *add_role_to_json(struct dynsec__role *role, bool verbose)
|
||||
{
|
||||
cJSON *j_role = NULL;
|
||||
|
||||
if(verbose){
|
||||
j_role = cJSON_CreateObject();
|
||||
if(j_role == NULL){
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(cJSON_AddStringToObject(j_role, "rolename", role->rolename) == NULL
|
||||
|| (role->text_name && cJSON_AddStringToObject(j_role, "textname", role->text_name) == NULL)
|
||||
|| (role->text_description && cJSON_AddStringToObject(j_role, "textdescription", role->text_description) == NULL)
|
||||
){
|
||||
|
||||
cJSON_Delete(j_role);
|
||||
return NULL;
|
||||
}
|
||||
if(add_acls_to_json(j_role, role)){
|
||||
cJSON_Delete(j_role);
|
||||
return NULL;
|
||||
}
|
||||
}else{
|
||||
j_role = cJSON_CreateString(role->rolename);
|
||||
if(j_role == NULL){
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return j_role;
|
||||
}
|
||||
|
||||
int dynsec_roles__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
bool verbose;
|
||||
struct dynsec__role *role, *role_tmp;
|
||||
cJSON *tree, *j_roles, *j_role, *jtmp, *j_data;
|
||||
int i, count, offset;
|
||||
char buf[30];
|
||||
|
||||
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, "listRoles", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
jtmp = cJSON_CreateString("listRoles");
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "listRoles", "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, "listRoles", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "data", j_data);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", HASH_CNT(hh, local_roles));
|
||||
cJSON_AddRawToObject(j_data, "totalCount", buf);
|
||||
|
||||
j_roles = cJSON_CreateArray();
|
||||
if(j_roles == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "listRoles", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToObject(j_data, "roles", j_roles);
|
||||
|
||||
i = 0;
|
||||
HASH_ITER(hh, local_roles, role, role_tmp){
|
||||
if(i>=offset){
|
||||
j_role = add_role_to_json(role, verbose);
|
||||
if(j_role == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "listRoles", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToArray(j_roles, j_role);
|
||||
|
||||
if(count >= 0){
|
||||
count--;
|
||||
if(count <= 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if(correlation_data){
|
||||
jtmp = cJSON_CreateString(correlation_data);
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "listRoles", "Internal error", correlation_data);
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "correlationData", jtmp);
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(j_responses, tree);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
char *topic;
|
||||
struct dynsec__role *role;
|
||||
cJSON *jtmp;
|
||||
struct dynsec__acl **acllist, *acl;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Role not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(command, "acltype");
|
||||
if(jtmp == NULL || !cJSON_IsString(jtmp)){
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid/missing acltype", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
if(!strcasecmp(jtmp->valuestring, "publishClientToBroker")){
|
||||
acllist = &role->acls.publish_c2b;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "publishBrokerToClient")){
|
||||
acllist = &role->acls.publish_b2c;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "subscribeLiteral")){
|
||||
acllist = &role->acls.subscribe_literal;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "subscribePattern")){
|
||||
acllist = &role->acls.subscribe_pattern;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "unsubscribeLiteral")){
|
||||
acllist = &role->acls.unsubscribe_literal;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "unsubscribePattern")){
|
||||
acllist = &role->acls.unsubscribe_pattern;
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Unknown acltype", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(command, "topic");
|
||||
if(jtmp && cJSON_IsString(jtmp)){
|
||||
topic = mosquitto_strdup(jtmp->valuestring);
|
||||
if(topic == NULL){
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid/missing topic", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
HASH_FIND(hh, *acllist, topic, strlen(topic), acl);
|
||||
if(acl){
|
||||
mosquitto_free(topic);
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "ACL with this topic already exists", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
acl = mosquitto_calloc(1, sizeof(struct dynsec__acl));
|
||||
if(acl == NULL){
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
acl->topic = topic;
|
||||
|
||||
json_get_int(command, "priority", &acl->priority, true, 0);
|
||||
json_get_bool(command, "allow", &acl->allow, true, false);
|
||||
|
||||
HASH_ADD_KEYPTR_INORDER(hh, *acllist, acl->topic, strlen(acl->topic), acl, insert_acl_cmp);
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "addRoleACL", NULL, correlation_data);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
struct dynsec__role *role;
|
||||
struct dynsec__acl **acllist, *acl;
|
||||
char *topic;
|
||||
cJSON *jtmp;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "createRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", "Role not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
jtmp = cJSON_GetObjectItem(command, "acltype");
|
||||
if(jtmp == NULL || !cJSON_IsString(jtmp)){
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", "Invalid/missing acltype", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
if(!strcasecmp(jtmp->valuestring, "publishClientToBroker")){
|
||||
acllist = &role->acls.publish_c2b;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "publishBrokerToClient")){
|
||||
acllist = &role->acls.publish_b2c;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "subscribeLiteral")){
|
||||
acllist = &role->acls.subscribe_literal;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "subscribePattern")){
|
||||
acllist = &role->acls.subscribe_pattern;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "unsubscribeLiteral")){
|
||||
acllist = &role->acls.unsubscribe_literal;
|
||||
}else if(!strcasecmp(jtmp->valuestring, "unsubscribePattern")){
|
||||
acllist = &role->acls.unsubscribe_pattern;
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", "Unknown acltype", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
if(json_get_string(command, "topic", &topic, false)){
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", "Invalid/missing topic", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
HASH_FIND(hh, *acllist, topic, strlen(topic), acl);
|
||||
if(acl){
|
||||
role__free_acl(acllist, acl);
|
||||
dynsec__config_save();
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", NULL, correlation_data);
|
||||
}else{
|
||||
dynsec__command_reply(j_responses, context, "removeRoleACL", "ACL not found", correlation_data);
|
||||
}
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
struct dynsec__role *role;
|
||||
cJSON *tree, *j_role, *jtmp, *j_data;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "getRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "getRole", "Role not found", correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
tree = cJSON_CreateObject();
|
||||
if(tree == NULL){
|
||||
dynsec__command_reply(j_responses, context, "getRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
jtmp = cJSON_CreateString("getRole");
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "getRole", "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, "getRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "data", j_data);
|
||||
|
||||
j_role = add_role_to_json(role, true);
|
||||
if(j_role == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "getRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
cJSON_AddItemToObject(j_data, "role", j_role);
|
||||
|
||||
if(correlation_data){
|
||||
jtmp = cJSON_CreateString(correlation_data);
|
||||
if(jtmp == NULL){
|
||||
cJSON_Delete(tree);
|
||||
dynsec__command_reply(j_responses, context, "getRole", "Internal error", correlation_data);
|
||||
return 1;
|
||||
}
|
||||
cJSON_AddItemToObject(tree, "correlationData", jtmp);
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(j_responses, tree);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_roles__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
|
||||
{
|
||||
char *rolename;
|
||||
char *text_name, *text_description;
|
||||
struct dynsec__role *role;
|
||||
char *str;
|
||||
cJSON *j_acls;
|
||||
struct dynsec__acl *tmp_publish_c2b, *tmp_publish_b2c;
|
||||
struct dynsec__acl *tmp_subscribe_literal, *tmp_subscribe_pattern;
|
||||
struct dynsec__acl *tmp_unsubscribe_literal, *tmp_unsubscribe_pattern;
|
||||
|
||||
if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", "Invalid/missing rolename", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
role = dynsec_roles__find(rolename);
|
||||
if(role == NULL){
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", "Role does not exist", correlation_data);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if(json_get_string(command, "textname", &text_name, true) == MOSQ_ERR_SUCCESS){
|
||||
str = mosquitto_strdup(text_name);
|
||||
if(str == NULL){
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
mosquitto_free(role->text_name);
|
||||
role->text_name = str;
|
||||
}
|
||||
|
||||
if(json_get_string(command, "textdescription", &text_description, true) == MOSQ_ERR_SUCCESS){
|
||||
str = mosquitto_strdup(text_description);
|
||||
if(str == NULL){
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
mosquitto_free(role->text_description);
|
||||
role->text_description = str;
|
||||
}
|
||||
|
||||
j_acls = cJSON_GetObjectItem(command, "acls");
|
||||
if(j_acls && cJSON_IsArray(j_acls)){
|
||||
if(dynsec_roles__acl_load(j_acls, "publishClientToBroker", &tmp_publish_c2b) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "publishBrokerToClient", &tmp_publish_b2c) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribeLiteral", &tmp_subscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "subscribePattern", &tmp_subscribe_pattern) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribeLiteral", &tmp_unsubscribe_literal) != 0
|
||||
|| dynsec_roles__acl_load(j_acls, "unsubscribePattern", &tmp_unsubscribe_pattern) != 0
|
||||
){
|
||||
|
||||
/* Free any that were successful */
|
||||
role__free_all_acls(&tmp_publish_c2b);
|
||||
role__free_all_acls(&tmp_publish_b2c);
|
||||
role__free_all_acls(&tmp_subscribe_literal);
|
||||
role__free_all_acls(&tmp_subscribe_pattern);
|
||||
role__free_all_acls(&tmp_unsubscribe_literal);
|
||||
role__free_all_acls(&tmp_unsubscribe_pattern);
|
||||
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", "Internal error", correlation_data);
|
||||
return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
role__free_all_acls(&role->acls.publish_c2b);
|
||||
role__free_all_acls(&role->acls.publish_b2c);
|
||||
role__free_all_acls(&role->acls.subscribe_literal);
|
||||
role__free_all_acls(&role->acls.subscribe_pattern);
|
||||
role__free_all_acls(&role->acls.unsubscribe_literal);
|
||||
role__free_all_acls(&role->acls.unsubscribe_pattern);
|
||||
|
||||
role->acls.publish_c2b = tmp_publish_c2b;
|
||||
role->acls.publish_b2c = tmp_publish_b2c;
|
||||
role->acls.subscribe_literal = tmp_subscribe_literal;
|
||||
role->acls.subscribe_pattern = tmp_subscribe_pattern;
|
||||
role->acls.unsubscribe_literal = tmp_unsubscribe_literal;
|
||||
role->acls.unsubscribe_pattern = tmp_unsubscribe_pattern;
|
||||
}
|
||||
|
||||
dynsec__config_save();
|
||||
|
||||
dynsec__command_reply(j_responses, context, "modifyRole", NULL, correlation_data);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
218
plugins/dynamic-security/sub_matches_sub.c
Normal file
218
plugins/dynamic-security/sub_matches_sub.c
Normal file
@ -0,0 +1,218 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static char *strtok_hier(char *str, char **saveptr)
|
||||
{
|
||||
char *c;
|
||||
|
||||
if(str != NULL){
|
||||
*saveptr = str;
|
||||
}
|
||||
|
||||
if(*saveptr == NULL){
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = strchr(*saveptr, '/');
|
||||
if(c){
|
||||
str = *saveptr;
|
||||
*saveptr = c+1;
|
||||
c[0] = '\0';
|
||||
}else if(*saveptr){
|
||||
/* No match, but surplus string */
|
||||
str = *saveptr;
|
||||
*saveptr = NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
static int count_hier_levels(const char *s)
|
||||
{
|
||||
int count = 1;
|
||||
const char *c = s;
|
||||
|
||||
while((c = strchr(c, '/')) && c[0]){
|
||||
c++;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
static bool hash_check(char *s, size_t *len)
|
||||
{
|
||||
if((*len) == 1 && s[0] == '#'){
|
||||
s[0] = '\0';
|
||||
(*len)--;
|
||||
return true;
|
||||
}else if((*len) > 1 && s[(*len)-2] == '/' && s[(*len)-1] == '#'){
|
||||
s[(*len)-2] = '\0';
|
||||
s[(*len)-1] = '\0';
|
||||
(*len) -= 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool sub_acl_check(const char *acl, const char *sub)
|
||||
{
|
||||
char *acl_local;
|
||||
char *sub_local;
|
||||
size_t acl_len, sub_len;
|
||||
bool acl_hash = false, sub_hash = false;
|
||||
int acl_levels, sub_levels;
|
||||
int i;
|
||||
char *acl_token, *sub_token;
|
||||
char *acl_saveptr, *sub_saveptr;
|
||||
|
||||
acl_len = strlen(acl);
|
||||
if(acl_len == 1 && acl[0] == '#'){
|
||||
return true;
|
||||
}
|
||||
|
||||
sub_len = strlen(sub);
|
||||
//mosquitto_validate_utf8(acl, acl_len);
|
||||
|
||||
acl_local = strdup(acl);
|
||||
sub_local = strdup(sub);
|
||||
if(acl_local == NULL || sub_local == NULL){
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return false;
|
||||
}
|
||||
|
||||
acl_hash = hash_check(acl_local, &acl_len);
|
||||
sub_hash = hash_check(sub_local, &sub_len);
|
||||
|
||||
if(sub_hash == true && acl_hash == false){
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return false;
|
||||
}
|
||||
|
||||
acl_levels = count_hier_levels(acl_local);
|
||||
sub_levels = count_hier_levels(sub_local);
|
||||
if(acl_levels > sub_levels){
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return false;
|
||||
}else if(sub_levels > acl_levels){
|
||||
if(acl_hash == false){
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
acl_saveptr = acl_local;
|
||||
sub_saveptr = sub_local;
|
||||
for(i=0; i<sub_levels; i++){
|
||||
acl_token = strtok_hier(acl_saveptr, &acl_saveptr);
|
||||
sub_token = strtok_hier(sub_saveptr, &sub_saveptr);
|
||||
|
||||
if(i<acl_levels &&
|
||||
(!strcmp(acl_token, "+")
|
||||
|| !strcmp(acl_token, sub_token))){
|
||||
|
||||
/* This level matches a single level wildcard, or is an exact
|
||||
* match, so carry on checking. */
|
||||
}else if(i>=acl_levels && acl_hash == true){
|
||||
/* The sub has more levels of hierarchy than the acl, but the acl
|
||||
* ends in a multi level wildcard so the match is fine. */
|
||||
}else{
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
free(acl_local);
|
||||
free(sub_local);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
|
||||
#define BLK "\e[0;30m"
|
||||
#define RED "\e[0;31m"
|
||||
#define GRN "\e[0;32m"
|
||||
#define YEL "\e[0;33m"
|
||||
#define BLU "\e[0;34m"
|
||||
#define MAG "\e[0;35m"
|
||||
#define CYN "\e[0;36m"
|
||||
#define WHT "\e[0;37m"
|
||||
#define RST "\e[0m"
|
||||
|
||||
void hier_test(const char *s, int expected)
|
||||
{
|
||||
int levels;
|
||||
|
||||
levels = count_hier_levels(s);
|
||||
printf("HIER %s %d:%d ", s, expected, levels);
|
||||
if(levels == expected){
|
||||
printf(GRN "passed" RST "\n");
|
||||
}else{
|
||||
printf(RED "failed" RST "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void test(const char *sub1, const char *sub2, bool expected)
|
||||
{
|
||||
bool result;
|
||||
|
||||
printf("ACL %s : %s ", sub1, sub2);
|
||||
result = sub_acl_check(sub1, sub2);
|
||||
if(result == expected){
|
||||
printf(GRN "passed\n" RST);
|
||||
}else{
|
||||
printf(RED "failed\n" RST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
hier_test("foo/+/bar", 3);
|
||||
hier_test("foo/#", 2);
|
||||
hier_test("foo/+/ba℞/#", 4);
|
||||
hier_test("foo/baz/ba℞", 3);
|
||||
hier_test("foo/+/ba℞/#", 4);
|
||||
hier_test("foo/baz/ba℞/+", 4);
|
||||
hier_test("foo/+/ba℞/#", 4);
|
||||
hier_test("foo/baz/ba℞/#", 4);
|
||||
hier_test("foo/+/ba℞/#", 4);
|
||||
hier_test("foo/baz/+/#", 4);
|
||||
hier_test("/+//#", 4);
|
||||
hier_test("/foo///#", 5);
|
||||
hier_test("#", 1);
|
||||
hier_test("+", 1);
|
||||
hier_test("/", 2);
|
||||
hier_test("////////////////////////////////////////////////////////////////////////////////////////////////////", 101);
|
||||
|
||||
test("foo/+/bar", "foo/#", false);
|
||||
test("foo/+/ba℞/#", "foo/baz/ba℞", true);
|
||||
test("foo/+/ba℞/#", "foo/baz/ba℞/+", true);
|
||||
test("foo/+/ba℞/#", "foo/baz/ba℞/#", true);
|
||||
test("foo/+/ba℞/#", "foo/baz/+/#", false);
|
||||
test("/+//#", "/foo///#", true);
|
||||
test("#", "#", true);
|
||||
test("#", "+", true);
|
||||
test("/#", "+", false);
|
||||
test("/#", "/+", true);
|
||||
test("/+", "#", false);
|
||||
test("/+", "+", false);
|
||||
test("+/+", "topic/topic", true);
|
||||
test("+/+", "topic/topic/", false);
|
||||
test("+", "#", false);
|
||||
test("+", "+", true);
|
||||
test("a/b/c/d/e", "a/b/c/d/e", true);
|
||||
test("a/b/ /d/e", "a/b/c/d/e", false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
@ -30,3 +30,4 @@ _mosquitto_property_free_all
|
||||
_mosquitto_realloc
|
||||
_mosquitto_set_username
|
||||
_mosquitto_strdup
|
||||
_mosquitto_topic_matches_sub
|
||||
|
@ -31,4 +31,5 @@
|
||||
mosquitto_realloc;
|
||||
mosquitto_set_username;
|
||||
mosquitto_strdup;
|
||||
mosquitto_topic_matches_sub;
|
||||
};
|
||||
|
@ -447,10 +447,12 @@ struct mosquitto_client_msg{
|
||||
bool dup;
|
||||
};
|
||||
|
||||
|
||||
struct mosquitto__unpwd{
|
||||
UT_hash_handle hh;
|
||||
char *username;
|
||||
char *password;
|
||||
char *clientid;
|
||||
#ifdef WITH_TLS
|
||||
unsigned char *salt;
|
||||
unsigned int password_len;
|
||||
@ -846,6 +848,8 @@ int mosquitto_psk_key_get_default(struct mosquitto_db *db, struct mosquitto *con
|
||||
int mosquitto_security_auth_start(struct mosquitto_db *db, struct mosquitto *context, bool reauth, const void *data_in, uint16_t data_in_len, void **data_out, uint16_t *data_out_len);
|
||||
int mosquitto_security_auth_continue(struct mosquitto_db *db, struct mosquitto *context, const void *data_in, uint16_t data_len, void **data_out, uint16_t *data_out_len);
|
||||
|
||||
void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item);
|
||||
|
||||
/* ============================================================
|
||||
* Session expiry
|
||||
* ============================================================ */
|
||||
|
@ -816,9 +816,7 @@ static int pwfile__parse(const char *file, struct mosquitto__unpwd **root)
|
||||
}
|
||||
|
||||
|
||||
#ifdef WITH_TLS
|
||||
|
||||
static void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item)
|
||||
void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item)
|
||||
{
|
||||
mosquitto__free(item->username);
|
||||
mosquitto__free(item->password);
|
||||
@ -828,6 +826,7 @@ static void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__
|
||||
}
|
||||
|
||||
|
||||
#ifdef WITH_TLS
|
||||
static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
|
||||
{
|
||||
struct mosquitto__unpwd *u, *tmp;
|
||||
|
143
test/broker/14-dynsec-client.py
Executable file
143
test/broker/14-dynsec-client.py
Executable file
@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
add_client_command = { "commands": [{
|
||||
"command": "createClient", "username": "user_one",
|
||||
"password": "password", "clientid": "cid",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"roleName": "", "correlationData": "2" }]
|
||||
}
|
||||
add_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
|
||||
add_client_repeat_response = {'responses':[{"command":"createClient","error":"Client already exists", "correlationData":"2"}]}
|
||||
|
||||
list_clients_command = { "commands": [{
|
||||
"command": "listClients", "verbose": False, "correlationData": "10"}]
|
||||
}
|
||||
list_clients_response = {'responses': [{"command": "listClients", "data":{"totalCount":1, "clients":["user_one"]},"correlationData":"10"}]}
|
||||
|
||||
list_clients_verbose_command = { "commands": [{
|
||||
"command": "listClients", "verbose": True, "correlationData": "20"}]
|
||||
}
|
||||
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{"totalCount":1, "clients":[
|
||||
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
|
||||
"groups":[], "roles":[]}]}, "correlationData":"20"}]}
|
||||
|
||||
|
||||
get_client_command = { "commands": [{
|
||||
"command": "getClient", "username": "user_one"}]}
|
||||
get_client_response = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
|
||||
'textName': 'Name', 'textDescription': 'Description', 'groups': [], 'roles': []}}}]}
|
||||
|
||||
set_client_password_command = {"commands": [{
|
||||
"command": "setClientPassword", "username": "user_one", "password": "password"}]}
|
||||
set_client_password_response = {"responses": [{"command":"setClientPassword"}]}
|
||||
|
||||
delete_client_command = { "commands": [{
|
||||
"command": "deleteClient", "username": "user_one"}]}
|
||||
delete_client_response = {'responses':[{'command': 'deleteClient'}]}
|
||||
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Add client
|
||||
command_check(sock, add_client_command, add_client_response)
|
||||
|
||||
# List clients non-verbose
|
||||
command_check(sock, list_clients_command, list_clients_response)
|
||||
|
||||
# List clients verbose
|
||||
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Get client
|
||||
command_check(sock, get_client_command, get_client_response)
|
||||
|
||||
# List clients non-verbose
|
||||
command_check(sock, list_clients_command, list_clients_response)
|
||||
|
||||
# List clients verbose
|
||||
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
|
||||
|
||||
# Add duplicate client
|
||||
command_check(sock, add_client_command, add_client_repeat_response)
|
||||
|
||||
# Set client password
|
||||
command_check(sock, set_client_password_command, set_client_password_response)
|
||||
|
||||
# Delete client
|
||||
command_check(sock, delete_client_command, delete_client_response)
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
160
test/broker/14-dynsec-group.py
Executable file
160
test/broker/14-dynsec-group.py
Executable file
@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response, msg=""):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(msg)
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
create_client_command = { "commands": [{
|
||||
"command": "createClient", "username": "user_one",
|
||||
"password": "password", "clientid": "cid",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"roleName": "", "correlationData": "2" }]}
|
||||
create_client_response = {'responses':[{"command":"createClient","correlationData":"2"}]}
|
||||
|
||||
create_group_command = { "commands": [{
|
||||
"command": "createGroup", "groupName": "group_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData":"3"}]}
|
||||
create_group_response = {'responses':[{"command":"createGroup","correlationData":"3"}]}
|
||||
create_group_repeat_response = {'responses':[{"command":"createGroup","error":"Group already exists","correlationData":"3"}]}
|
||||
|
||||
list_groups_command = { "commands": [{
|
||||
"command": "listGroups", "verbose": False, "correlationData": "10"}]}
|
||||
list_groups_response = {'responses':[{"command": "listGroups", "data":{"totalCount":1, "groups":["group_one"]},"correlationData":"10"}]}
|
||||
|
||||
list_groups_verbose_command = { "commands": [{
|
||||
"command": "listGroups", "verbose": True, "correlationData": "15"}]}
|
||||
list_groups_verbose_response = {'responses':[{'command': 'listGroups', 'data': {"totalCount":1, 'groups':
|
||||
[{'groupName': 'group_one', 'textName': 'Name', 'textDescription': 'Description', 'clients': [
|
||||
{"username":"user_one"}], "roles":[]}]},
|
||||
'correlationData': '15'}]}
|
||||
|
||||
list_clients_verbose_command = { "commands": [{
|
||||
"command": "listClients", "verbose": True, "correlationData": "20"}]}
|
||||
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{"totalCount":1, "clients":[
|
||||
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
|
||||
"groups":[{"groupName":"group_one"}], "roles":[]}]}, "correlationData":"20"}]}
|
||||
|
||||
get_group_command = { "commands": [{"command": "getGroup", "groupName":"group_one"}]}
|
||||
get_group_response = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
|
||||
'textName':'Name', 'textDescription':'Description', 'clients': [{"username":"user_one"}], 'roles': []}}}]}
|
||||
|
||||
add_client_to_group_command = {"commands": [{"command":"addGroupClient", "username":"user_one",
|
||||
"groupName": "group_one", "correlationData":"1234"}]}
|
||||
add_client_to_group_response = {'responses':[{'command': 'addGroupClient', 'correlationData': '1234'}]}
|
||||
|
||||
remove_client_from_group_command = {"commands": [{"command":"removeGroupClient", "username":"user_one",
|
||||
"groupName": "group_one", "correlationData":"4321"}]}
|
||||
remove_client_from_group_response = {'responses':[{'command': 'removeGroupClient', 'correlationData': '4321'}]}
|
||||
|
||||
delete_group_command = {"commands": [{"command":"deleteGroup", "groupName":"group_one", "correlationData":"5678"}]}
|
||||
delete_group_response = {'responses':[{"command":"deleteGroup", "correlationData":"5678"}]}
|
||||
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Add client
|
||||
command_check(sock, create_client_command, create_client_response)
|
||||
|
||||
# Add group
|
||||
command_check(sock, create_group_command, create_group_response)
|
||||
|
||||
# Add client to group
|
||||
command_check(sock, add_client_to_group_command, add_client_to_group_response)
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command, get_group_response)
|
||||
|
||||
# List groups non-verbose
|
||||
command_check(sock, list_groups_command, list_groups_response)
|
||||
|
||||
# List groups verbose
|
||||
command_check(sock, list_groups_verbose_command, list_groups_verbose_response, "list groups")
|
||||
|
||||
# List clients verbose
|
||||
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Add duplicate group
|
||||
command_check(sock, create_group_command, create_group_repeat_response)
|
||||
|
||||
# Remove client from group
|
||||
command_check(sock, remove_client_from_group_command, remove_client_from_group_response)
|
||||
|
||||
# Add client back to group
|
||||
command_check(sock, add_client_to_group_command, add_client_to_group_response)
|
||||
|
||||
# Delete group entirely
|
||||
command_check(sock, delete_group_command, delete_group_response)
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
219
test/broker/14-dynsec-modify-client.py
Executable file
219
test/broker/14-dynsec-modify-client.py
Executable file
@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response, msg=""):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(msg)
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
create_client_command = { "commands": [{
|
||||
"command": "createClient", "username": "user_one",
|
||||
"password": "password", "clientid": "cid",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData": "2" }]
|
||||
}
|
||||
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
|
||||
|
||||
create_groups_command = { "commands": [
|
||||
{
|
||||
"command": "createGroup", "groupName": "group_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData": "12"
|
||||
},
|
||||
{
|
||||
"command": "createGroup", "groupName": "group_two",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData": "13"
|
||||
}
|
||||
]
|
||||
}
|
||||
create_groups_response = {'responses': [
|
||||
{'command': 'createGroup', 'correlationData': '12'},
|
||||
{'command': 'createGroup', 'correlationData': '13'}
|
||||
]}
|
||||
|
||||
create_roles_command = { "commands": [
|
||||
{
|
||||
"command": "createRole", "roleName": "role_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[], "correlationData": "21"
|
||||
},
|
||||
{
|
||||
"command": "createRole", "roleName": "role_two",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[], "correlationData": "22"
|
||||
},
|
||||
{
|
||||
"command": "createRole", "roleName": "role_three",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[], "correlationData": "23"
|
||||
}
|
||||
]
|
||||
}
|
||||
create_roles_response = {'responses': [
|
||||
{'command': 'createRole', 'correlationData': '21'},
|
||||
{'command': 'createRole', 'correlationData': '22'},
|
||||
{'command': 'createRole', 'correlationData': '23'}
|
||||
]}
|
||||
|
||||
modify_client_command1 = { "commands": [{
|
||||
"command": "modifyClient", "username": "user_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"roles":[
|
||||
{'roleName':'role_one', 'priority':2},
|
||||
{'roleName':'role_two'},
|
||||
{'roleName':'role_three', 'priority':10}
|
||||
],
|
||||
"groups":[
|
||||
{'groupName':'group_one', 'priority':3},
|
||||
{'groupName':'group_two', 'priority':8}
|
||||
],
|
||||
"correlationData": "3" }]
|
||||
}
|
||||
modify_client_response1 = {'responses': [{'command': 'modifyClient', 'correlationData': '3'}]}
|
||||
|
||||
modify_client_command2 = { "commands": [{
|
||||
"command": "modifyClient", "username": "user_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"groups":[],
|
||||
"correlationData": "4" }]
|
||||
}
|
||||
modify_client_response2 = {'responses': [{'command': 'modifyClient', 'correlationData': '4'}]}
|
||||
|
||||
|
||||
get_client_command1 = { "commands": [{
|
||||
"command": "getClient", "username": "user_one"}]}
|
||||
get_client_response1 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
|
||||
'textName': 'Name', 'textDescription': 'Description',
|
||||
'groups': [],
|
||||
'roles': [],
|
||||
}}}]}
|
||||
|
||||
get_client_command2 = { "commands": [{
|
||||
"command": "getClient", "username": "user_one"}]}
|
||||
get_client_response2 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'groups': [
|
||||
{'groupName':'group_two', 'priority':8},
|
||||
{'groupName':'group_one', 'priority':3}
|
||||
],
|
||||
'roles': [
|
||||
{'roleName':'role_three', 'priority':10},
|
||||
{'roleName':'role_one', 'priority':2},
|
||||
{'roleName':'role_two'}
|
||||
]}}}]}
|
||||
|
||||
get_client_command3 = { "commands": [{
|
||||
"command": "getClient", "username": "user_one"}]}
|
||||
get_client_response3 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'groups': [],
|
||||
'roles': [
|
||||
{'roleName':'role_three', 'priority':10},
|
||||
{'roleName':'role_one', 'priority':2},
|
||||
{'roleName':'role_two'}
|
||||
]}}}]}
|
||||
|
||||
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Create client
|
||||
command_check(sock, create_client_command, create_client_response)
|
||||
|
||||
# Create groups
|
||||
command_check(sock, create_groups_command, create_groups_response)
|
||||
|
||||
# Create role
|
||||
command_check(sock, create_roles_command, create_roles_response)
|
||||
|
||||
# Get client
|
||||
command_check(sock, get_client_command1, get_client_response1, "get client 1")
|
||||
|
||||
# Modify client - with groups
|
||||
command_check(sock, modify_client_command1, modify_client_response1)
|
||||
|
||||
# Get client
|
||||
command_check(sock, get_client_command2, get_client_response2, "get client 2a")
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Get client
|
||||
command_check(sock, get_client_command2, get_client_response2, "get client 2b")
|
||||
|
||||
# Modify client - without groups
|
||||
command_check(sock, modify_client_command2, modify_client_response2)
|
||||
|
||||
# Get client
|
||||
command_check(sock, get_client_command3, get_client_response3, "get client 3")
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
212
test/broker/14-dynsec-modify-group.py
Executable file
212
test/broker/14-dynsec-modify-group.py
Executable file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response, msg=""):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(msg)
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
create_client_command = { "commands": [{
|
||||
"command": "createClient", "username": "user_one",
|
||||
"password": "password", "clientid": "cid",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData": "2" }]
|
||||
}
|
||||
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
|
||||
|
||||
create_group_command = { "commands": [{
|
||||
"command": "createGroup", "groupName": "group_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"roleName": "", "correlationData": "2" }]
|
||||
}
|
||||
create_group_response = {'responses': [{'command': 'createGroup', 'correlationData': '2'}]}
|
||||
|
||||
create_role_command = { "commands": [
|
||||
{
|
||||
"command": "createRole", "roleName": "role_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[], "correlationData": "2"
|
||||
},
|
||||
{
|
||||
"command": "createRole", "roleName": "role_two",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[], "correlationData": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
create_role_response = {'responses': [
|
||||
{'command': 'createRole', 'correlationData': '2'},
|
||||
{'command': 'createRole', 'correlationData': '3'}
|
||||
]}
|
||||
|
||||
modify_group_command1 = { "commands": [{
|
||||
"command": "modifyGroup", "groupName": "group_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"roles":[{'roleName':'role_one'}],
|
||||
"clients":[{'username':'user_one'}],
|
||||
"correlationData": "3" }]
|
||||
}
|
||||
modify_group_response1 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
|
||||
|
||||
modify_group_command2 = { "commands": [{
|
||||
"command": "modifyGroup", "groupName": "group_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"roles":[
|
||||
{'roleName':'role_one', 'priority':99},
|
||||
{'roleName':'role_two', 'priority':87}
|
||||
],
|
||||
"clients":[],
|
||||
"correlationData": "3" }]
|
||||
}
|
||||
modify_group_response2 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
|
||||
|
||||
modify_group_command3 = { "commands": [{
|
||||
"command": "modifyGroup", "groupName": "group_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"roles":[],
|
||||
"clients":[],
|
||||
"correlationData": "3" }]
|
||||
}
|
||||
modify_group_response3 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
|
||||
|
||||
|
||||
get_group_command1 = { "commands": [{
|
||||
"command": "getGroup", "groupName": "group_one"}]}
|
||||
get_group_response1 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
|
||||
'textName': 'Name', 'textDescription': 'Description',
|
||||
'clients':[],
|
||||
'roles': []}}}]}
|
||||
|
||||
get_group_command2 = { "commands": [{
|
||||
"command": "getGroup", "groupName": "group_one"}]}
|
||||
get_group_response2 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'clients':[{'username':'user_one'}],
|
||||
'roles': [{'roleName':'role_one'}]}}}]}
|
||||
|
||||
get_group_command3 = { "commands": [{
|
||||
"command": "getGroup", "groupName": "group_one"}]}
|
||||
get_group_response3 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'clients':[],
|
||||
'roles': [
|
||||
{'roleName':'role_one', 'priority':99},
|
||||
{'roleName':'role_two', 'priority':87}
|
||||
]}}}]}
|
||||
|
||||
get_group_command4 = { "commands": [{
|
||||
"command": "getGroup", "groupName": "group_one"}]}
|
||||
get_group_response4 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'clients':[],
|
||||
'roles': []}}}]}
|
||||
|
||||
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Create client
|
||||
command_check(sock, create_client_command, create_client_response)
|
||||
|
||||
# Create group
|
||||
command_check(sock, create_group_command, create_group_response)
|
||||
|
||||
# Create role
|
||||
command_check(sock, create_role_command, create_role_response)
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command1, get_group_response1, "get group 1")
|
||||
|
||||
# Modify group
|
||||
command_check(sock, modify_group_command1, modify_group_response1)
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command2, get_group_response2, "get group 2a")
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command2, get_group_response2, "get group 2b")
|
||||
|
||||
# Modify group
|
||||
command_check(sock, modify_group_command2, modify_group_response2)
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command3, get_group_response3, "get group 3")
|
||||
|
||||
# Modify group
|
||||
command_check(sock, modify_group_command3, modify_group_response3)
|
||||
|
||||
# Get group
|
||||
command_check(sock, get_group_command4, get_group_response4, "get group 4")
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
160
test/broker/14-dynsec-modify-role.py
Executable file
160
test/broker/14-dynsec-modify-role.py
Executable file
@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
create_role_command = { "commands": [{
|
||||
"command": "createRole", "roleName": "role_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"acls":[
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"allow": True,
|
||||
"topic": "topic/#",
|
||||
"priority": 8
|
||||
},
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"allow": True,
|
||||
"topic": "topic/2/#",
|
||||
"priority": 9
|
||||
}
|
||||
], "correlationData": "2" }]
|
||||
}
|
||||
create_role_response = {'responses': [{'command': 'createRole', 'correlationData': '2'}]}
|
||||
|
||||
modify_role_command = { "commands": [{
|
||||
"command": "modifyRole", "roleName": "role_one",
|
||||
"textName": "Modified name", "textDescription": "Modified description",
|
||||
"correlationData": "3" }]
|
||||
}
|
||||
modify_role_response = {'responses': [{'command': 'modifyRole', 'correlationData': '3'}]}
|
||||
|
||||
|
||||
get_role_command1 = { "commands": [{"command": "getRole", "roleName": "role_one"}]}
|
||||
get_role_response1 = {'responses':[{'command': 'getRole', 'data': {'role': {'roleName': 'role_one',
|
||||
'textName': 'Name', 'textDescription': 'Description',
|
||||
'acls': [
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"topic": "topic/2/#",
|
||||
"allow": True,
|
||||
"priority": 9
|
||||
},
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"topic": "topic/#",
|
||||
"allow": True,
|
||||
"priority": 8
|
||||
}
|
||||
]}}}]}
|
||||
|
||||
get_role_command2 = { "commands": [{
|
||||
"command": "getRole", "roleName": "role_one"}]}
|
||||
get_role_response2 = {'responses':[{'command': 'getRole', 'data': {'role': {'roleName': 'role_one',
|
||||
'textName': 'Modified name', 'textDescription': 'Modified description',
|
||||
'acls': [
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"topic": "topic/2/#",
|
||||
"allow": True,
|
||||
"priority": 9
|
||||
},
|
||||
{
|
||||
"aclType": "publishClientToBroker",
|
||||
"topic": "topic/#",
|
||||
"allow": True,
|
||||
"priority": 8
|
||||
}
|
||||
]}}}]}
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Add role
|
||||
command_check(sock, create_role_command, create_role_response)
|
||||
|
||||
# Get role
|
||||
command_check(sock, get_role_command1, get_role_response1)
|
||||
|
||||
# Modify role
|
||||
command_check(sock, modify_role_command, modify_role_response)
|
||||
|
||||
# Get role
|
||||
command_check(sock, get_role_command2, get_role_response2)
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Get role
|
||||
command_check(sock, get_role_command2, get_role_response2)
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
220
test/broker/14-dynsec-role.py
Executable file
220
test/broker/14-dynsec-role.py
Executable file
@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
import json
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
|
||||
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
|
||||
|
||||
def command_check(sock, command_payload, expected_response, msg=""):
|
||||
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
|
||||
sock.send(command_packet)
|
||||
response = json.loads(mosq_test.read_publish(sock))
|
||||
if response != expected_response:
|
||||
print(msg)
|
||||
print(expected_response)
|
||||
print(response)
|
||||
raise ValueError(response)
|
||||
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
create_client_command = { "commands": [{
|
||||
"command": "createClient", "username": "user_one",
|
||||
"password": "password", "clientid": "cid",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"roleName": "", "correlationData": "2" }]
|
||||
}
|
||||
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
|
||||
|
||||
create_group_command = { "commands": [{
|
||||
"command": "createGroup", "groupName": "group_one",
|
||||
"textName": "Name", "textDescription": "Description",
|
||||
"correlationData":"3"}]}
|
||||
create_group_response = {'responses':[{"command":"createGroup","correlationData":"3"}]}
|
||||
|
||||
create_role_command = { "commands": [{'command': 'createRole', 'correlationData': '3',
|
||||
"roleName": "basic", "acls":[
|
||||
{"aclType":"publishClientToBroker", "topic": "out/#", "priority":3, "allow": True}], "textName":"name", "textDescription":"desc"
|
||||
}]}
|
||||
create_role_response = {'responses': [{'command': 'createRole', 'correlationData': '3'}]}
|
||||
|
||||
|
||||
add_role_to_client_command = {"commands": [{'command': 'addClientRole', "username": "user_one",
|
||||
"roleName": "basic"}]}
|
||||
add_role_to_client_response = {'responses': [{'command': 'addClientRole'}]}
|
||||
|
||||
add_role_to_group_command = {"commands": [{'command': 'addGroupRole', "groupName": "group_one",
|
||||
"roleName": "basic"}]}
|
||||
add_role_to_group_response = {'responses': [{'command': 'addGroupRole'}]}
|
||||
|
||||
|
||||
list_roles_verbose_command1 = { "commands": [{
|
||||
"command": "listRoles", "verbose": True, "correlationData": "21"}]
|
||||
}
|
||||
list_roles_verbose_response1 = {'responses': [{'command': 'listRoles', 'data':
|
||||
{'totalCount':1, 'roles': [{'roleName': 'basic', "textName": "name", "textDescription": "desc",
|
||||
'acls': [{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True}]
|
||||
}]}, 'correlationData': '21'}]}
|
||||
|
||||
add_acl_command = {"commands": [{'command': "addRoleACL", "roleName":"basic", "aclType":"subscribeLiteral",
|
||||
"topic":"basic/out", "priority":1, "allow":True}]}
|
||||
add_acl_response = {'responses': [{'command': 'addRoleACL'}]}
|
||||
|
||||
list_roles_verbose_command2 = { "commands": [{
|
||||
"command": "listRoles", "verbose": True, "correlationData": "22"}]
|
||||
}
|
||||
list_roles_verbose_response2 = {'responses': [{'command': 'listRoles', 'data': {'totalCount':1, 'roles':
|
||||
[{'roleName': 'basic', 'textName': 'name', 'textDescription': 'desc', 'acls':
|
||||
[{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True},
|
||||
{'aclType':'subscribeLiteral', 'topic': 'basic/out', 'priority': 1, 'allow': True}],
|
||||
}]}, 'correlationData': '22'}]}
|
||||
|
||||
get_role_command = {"commands": [{'command': "getRole", "roleName":"basic"}]}
|
||||
get_role_response = {'responses': [{'command': 'getRole', 'data': {'role':
|
||||
{'roleName': 'basic', 'textName': 'name', 'textDescription': 'desc', 'acls':
|
||||
[{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True},
|
||||
{'aclType':'subscribeLiteral', 'topic': 'basic/out', 'priority': 1, 'allow': True}],
|
||||
}}}]}
|
||||
|
||||
remove_acl_command = {"commands": [{'command': "removeRoleACL", "roleName":"basic", "aclType":"subscribeLiteral",
|
||||
"topic":"basic/out"}]}
|
||||
remove_acl_response = {'responses': [{'command': 'removeRoleACL'}]}
|
||||
|
||||
delete_role_command = {"commands": [{'command': "deleteRole", "roleName":"basic"}]}
|
||||
delete_role_response = {"responses": [{"command": "deleteRole"}]}
|
||||
|
||||
list_clients_verbose_command = { "commands": [{
|
||||
"command": "listClients", "verbose": True, "correlationData": "20"}]
|
||||
}
|
||||
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{'totalCount':1, "clients":[
|
||||
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
|
||||
"groups":[], "roles":[{'roleName':'basic'}]}]}, "correlationData":"20"}]}
|
||||
|
||||
list_groups_verbose_command = { "commands": [{
|
||||
"command": "listGroups", "verbose": True, "correlationData": "20"}]
|
||||
}
|
||||
list_groups_verbose_response = {'responses':[{"command": "listGroups", "data":{'totalCount':1, "groups":[
|
||||
{"groupName":"group_one", "textName":"Name", "textDescription":"Description",
|
||||
"clients":[], "roles":[{'roleName':'basic'}]}]}, "correlationData":"20"}]}
|
||||
|
||||
remove_role_from_client_command = {"commands": [{'command': 'removeClientRole', "username": "user_one",
|
||||
"roleName": "basic"}]}
|
||||
remove_role_from_client_response = {'responses': [{'command': 'removeClientRole'}]}
|
||||
|
||||
remove_role_from_group_command = {"commands": [{'command': 'removeGroupRole', "groupName": "group_one",
|
||||
"roleName": "basic"}]}
|
||||
remove_role_from_group_response = {'responses': [{'command': 'removeGroupRole'}]}
|
||||
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
try:
|
||||
os.mkdir(str(port))
|
||||
with open("%d/dynamic-security.json" % port, 'w') as f:
|
||||
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
|
||||
except FileExistsError:
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# Create client
|
||||
command_check(sock, create_client_command, create_client_response)
|
||||
|
||||
# Create group
|
||||
command_check(sock, create_group_command, create_group_response)
|
||||
|
||||
# Create role
|
||||
command_check(sock, create_role_command, create_role_response)
|
||||
|
||||
# Add role to client
|
||||
command_check(sock, add_role_to_client_command, add_role_to_client_response)
|
||||
|
||||
# Add role to group
|
||||
command_check(sock, add_role_to_group_command, add_role_to_group_response)
|
||||
|
||||
# List clients verbose
|
||||
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
|
||||
|
||||
# List groups verbose
|
||||
command_check(sock, list_groups_verbose_command, list_groups_verbose_response)
|
||||
|
||||
# List roles verbose 1
|
||||
command_check(sock, list_roles_verbose_command1, list_roles_verbose_response1, "list roles verbose 1a")
|
||||
|
||||
# Add ACL
|
||||
command_check(sock, add_acl_command, add_acl_response)
|
||||
|
||||
# List roles verbose 2
|
||||
command_check(sock, list_roles_verbose_command2, list_roles_verbose_response2, "list roles verbose 2a")
|
||||
|
||||
# Kill broker and restart, checking whether our changes were saved.
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
# List roles verbose 2
|
||||
command_check(sock, list_roles_verbose_command2, list_roles_verbose_response2, "list roles verbose 2b")
|
||||
|
||||
# Get role
|
||||
command_check(sock, get_role_command, get_role_response)
|
||||
|
||||
# Remove ACL
|
||||
command_check(sock, remove_acl_command, remove_acl_response)
|
||||
|
||||
# List roles verbose 1
|
||||
command_check(sock, list_roles_verbose_command1, list_roles_verbose_response1, "list roles verbose 1b")
|
||||
|
||||
# Remove role from client
|
||||
command_check(sock, remove_role_from_client_command, remove_role_from_client_response)
|
||||
|
||||
# Remove role from group
|
||||
command_check(sock, remove_role_from_group_command, remove_role_from_group_response)
|
||||
|
||||
# Delete role
|
||||
command_check(sock, delete_role_command, delete_role_response)
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
try:
|
||||
os.remove(f"{port}/dynamic-security.json")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.rmdir(f"{port}")
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("port %d\n" % (port))
|
||||
f.write("auth_plugin c/plugin_control.so\n")
|
||||
f.write("allow_anonymous true\n")
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
|
||||
rc = 1
|
||||
keepalive = 10
|
||||
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 2
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/user-management/v1", 1)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1)
|
||||
|
||||
mid = 3
|
||||
publish_packet = mosq_test.gen_publish(topic="$CONTROL/user-management/v1", qos=1, payload="payload contents", retain=1, mid=mid)
|
||||
puback_packet = mosq_test.gen_puback(mid)
|
||||
|
||||
mid = 1
|
||||
publish_packet_recv = mosq_test.gen_publish(topic="$CONTROL/user-management/v1", qos=0, payload="payload contents", retain=0)
|
||||
|
||||
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
sock.send(publish_packet)
|
||||
mosq_test.receive_unordered(sock, puback_packet, publish_packet_recv, "puback/publish_receive")
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
os.remove(conf_file)
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
|
||||
|
||||
exit(rc)
|
||||
|
@ -220,4 +220,9 @@ endif
|
||||
./13-malformed-unsubscribe-v5.py
|
||||
|
||||
14 :
|
||||
#./14-plugin-register-control.py
|
||||
#./14-dynsec-client.py
|
||||
#./14-dynsec-group.py
|
||||
#./14-dynsec-role.py
|
||||
#./14-dynsec-modify-client.py
|
||||
#./14-dynsec-modify-group.py
|
||||
#./14-dynsec-modify-role.py
|
||||
|
@ -188,7 +188,12 @@ tests = [
|
||||
(1, './13-malformed-subscribe-v5.py'),
|
||||
(1, './13-malformed-unsubscribe-v5.py'),
|
||||
|
||||
#(1, './14-plugin-register-control.py'),
|
||||
#(1, './14-dynsec-client.py'),
|
||||
#(1, './14-dynsec-group.py'),
|
||||
#(1, './14-dynsec-role.py'),
|
||||
#(1, './14-dynsec-modify-client.py'),
|
||||
#(1, './14-dynsec-modify-group.py'),
|
||||
#(1, './14-dynsec-modify-role.py'),
|
||||
]
|
||||
|
||||
ptest.run_tests(tests)
|
||||
|
@ -364,6 +364,51 @@ def to_string(packet):
|
||||
(cmd, rl) = struct.unpack('!BB', packet)
|
||||
return "AUTH, rl="+str(rl)
|
||||
|
||||
|
||||
def read_varint(sock, rl):
|
||||
varint = 0
|
||||
multiplier = 1
|
||||
while True:
|
||||
byte = sock.recv(1)
|
||||
byte, = struct.unpack("!B", byte)
|
||||
varint += (byte & 127)*multiplier
|
||||
multiplier *= 128
|
||||
rl -= 1
|
||||
if byte & 128 == 0x00:
|
||||
return (varint, rl)
|
||||
|
||||
|
||||
def mqtt_read_string(sock, rl):
|
||||
slen = sock.recv(2)
|
||||
slen, = struct.unpack("!H", slen)
|
||||
payload = sock.recv(slen)
|
||||
payload, = struct.unpack("!%ds" % (slen), payload)
|
||||
rl -= (2 + slen)
|
||||
return (payload, rl)
|
||||
|
||||
|
||||
def read_publish(sock, proto_ver=4):
|
||||
cmd, = struct.unpack("!B", sock.recv(1))
|
||||
if cmd & 0xF0 != 0x30:
|
||||
raise ValueError
|
||||
|
||||
qos = (cmd & 0x06) >> 1
|
||||
rl, t = read_varint(sock, 0)
|
||||
topic, rl = mqtt_read_string(sock, rl)
|
||||
|
||||
if qos > 0:
|
||||
sock.recv(2)
|
||||
rl -= 1
|
||||
|
||||
if proto_ver == 5:
|
||||
proplen, rl = read_varint(sock, rl)
|
||||
sock.recv(proplen)
|
||||
rl -= proplen
|
||||
|
||||
payload = sock.recv(rl).decode('utf-8')
|
||||
return payload
|
||||
|
||||
|
||||
def gen_connect(client_id, clean_session=True, keepalive=60, username=None, password=None, will_topic=None, will_qos=0, will_retain=False, will_payload=b"", proto_ver=4, connect_reserved=False, properties=b"", will_properties=b"", session_expiry=-1):
|
||||
if (proto_ver&0x7F) == 3 or proto_ver == 0:
|
||||
remaining_length = 12
|
||||
|
Loading…
x
Reference in New Issue
Block a user