mirror of
https://github.com/eclipse-mosquitto/mosquitto.git
synced 2026-09-23 00:24:47 +08:00
New dynamic security plugin.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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})
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+14
-4
@@ -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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.. title: mosquitto_ctrl man page
|
||||
.. slug: mosquitto_ctrl-1
|
||||
.. category: man
|
||||
.. type: man
|
||||
.. pretty_url: False
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
+6
-1
@@ -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.
|
||||
|
||||
@@ -12,4 +17,4 @@ This is an **example** plugin to demonstrate how it is possible to modify the pa
|
||||
|
||||
If you are considering using this feature, you should be very certain you have verified the payload is the correct format before modifying it.
|
||||
|
||||
This plugin adds the text string "hello " to the beginning of each payload, so with anything other than simple plain text messages it will corrupt the payload contents.
|
||||
This plugin adds the text string "hello " to the beginning of each payload, so with anything other than simple plain text messages it will corrupt the payload contents.
|
||||
|
||||
@@ -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}")
|
||||
@@ -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"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
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;
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
Executable
+143
@@ -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)
|
||||
Executable
+160
@@ -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)
|
||||
Executable
+219
@@ -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)
|
||||
Executable
+212
@@ -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)
|
||||
Executable
+160
@@ -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)
|
||||
Executable
+220
@@ -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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user