New dynamic security plugin.

This commit is contained in:
Roger A. Light
2020-10-27 00:54:55 +00:00
parent e82ee879d5
commit fdff255916
53 changed files with 9165 additions and 80 deletions
+2
View File
@@ -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
+7
View File
@@ -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
View File
@@ -1 +1,2 @@
add_subdirectory(mosquitto_ctrl)
add_subdirectory(mosquitto_passwd)
+1
View File
@@ -1,5 +1,6 @@
DIRS= \
db_dump \
mosquitto_ctrl \
mosquitto_passwd
.PHONY : all binary check clean reallyclean test install uninstall
+29
View File
@@ -0,0 +1,29 @@
if (WITH_TLS AND CJSON_FOUND)
include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/include
${mosquitto_SOURCE_DIR}/lib ${mosquitto_SOURCE_DIR}/src
${OPENSSL_INCLUDE_DIR} ${STDBOOL_H_PATH} ${STDINT_H_PATH}
${CJSON_INCLUDE_DIRS})
add_executable(mosquitto_ctrl
mosquitto_ctrl.c mosquitto_ctrl.h
client.c
dynsec.c
dynsec_client.c
dynsec_group.c
dynsec_role.c
../../lib/memory_mosq.c ../../lib/memory_mosq.h
../../src/memory_public.c
options.c
../../src/password_mosq.c ../../src/password_mosq.h
)
if (WITH_STATIC_LIBRARIES)
target_link_libraries(mosquitto_ctrl libmosquitto_static)
else()
target_link_libraries(mosquitto_ctrl libmosquitto)
endif()
target_link_libraries(mosquitto_ctrl ${OPENSSL_LIBRARIES} ${CJSON_LIBRARIES})
install(TARGETS mosquitto_ctrl RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif (WITH_TLS AND CJSON_FOUND)
+79
View File
@@ -0,0 +1,79 @@
include ../../config.mk
.PHONY: all install uninstall clean reallyclean
ifeq ($(WITH_SHARED_LIBRARIES),yes)
LIBMOSQ:=../../lib/libmosquitto.so.${SOVERSION}
else
LIBMOSQ:=../../lib/libmosquitto.a
endif
LOCAL_CPPFLAGS:=-I/usr/include/cjson -I/usr/local/include/cjson
OBJS= mosquitto_ctrl.o \
client.o \
dynsec.o \
dynsec_client.o \
dynsec_group.o \
dynsec_role.o \
memory_mosq.o \
memory_public.o \
options.o \
password_mosq.o
ifeq ($(WITH_TLS),yes)
ifeq ($(WITH_CJSON),yes)
TARGET:=mosquitto_ctrl
endif
endif
all : $(TARGET)
mosquitto_ctrl : ${OBJS}
${CROSS_COMPILE}${CC} ${APP_LDFLAGS} $^ -o $@ $(PASSWD_LDADD) $(LIBMOSQ) -lcjson
mosquitto_ctrl.o : mosquitto_ctrl.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
client.o : client.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
dynsec.o : dynsec.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
dynsec_client.o : dynsec_client.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
dynsec_group.o : dynsec_group.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
dynsec_role.o : dynsec_role.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
memory_mosq.o : ../../lib/memory_mosq.c
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
memory_public.o : ../../src/memory_public.c
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
options.o : options.c mosquitto_ctrl.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
misc_mosq.o : ../../lib/misc_mosq.c ../../lib/misc_mosq.h
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
password_mosq.o : ../../src/password_mosq.c ../../src/password_mosq.h
${CROSS_COMPILE}${CC} $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@
install : all
$(INSTALL) -d "${DESTDIR}$(prefix)/bin"
$(INSTALL) ${STRIP_OPTS} mosquitto_ctrl "${DESTDIR}${prefix}/bin/mosquitto_ctrl"
uninstall :
-rm -f "${DESTDIR}${prefix}/bin/mosquitto_ctrl"
clean :
-rm -f *.o mosquitto_ctrl *.gcda *.gcno
reallyclean : clean
-rm -rf *.orig *.db
+147
View File
@@ -0,0 +1,147 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <mosquitto.h>
#include <mqtt_protocol.h>
#include "mosquitto_ctrl.h"
static int run = 1;
static void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg, const mosquitto_property *properties)
{
struct mosq_ctrl *ctrl = obj;
if(ctrl->payload_callback){
ctrl->payload_callback(ctrl, msg->payloadlen, msg->payload);
}
mosquitto_disconnect_v5(mosq, 0, NULL);
run = 0;
}
static void on_publish(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties)
{
if(reason_code > 127){
fprintf(stderr, "Publish error: %s\n", mosquitto_reason_string(reason_code));
run = 0;
mosquitto_disconnect_v5(mosq, 0, NULL);
}
}
static void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos, const mosquitto_property *properties)
{
struct mosq_ctrl *ctrl = obj;
char *json_str;
if(qos_count == 1){
if(granted_qos[0] < 128){
/* Success */
json_str = cJSON_PrintUnformatted(ctrl->j_tree);
cJSON_Delete(ctrl->j_tree);
ctrl->j_tree = NULL;
if(json_str == NULL){
fprintf(stderr, "Error: Out of memory.\n");
run = 0;
mosquitto_disconnect_v5(mosq, 0, NULL);
}
mosquitto_publish(mosq, NULL, ctrl->request_topic, strlen(json_str), json_str, ctrl->cfg.qos, 0);
free(ctrl->request_topic);
ctrl->request_topic = NULL;
free(json_str);
}else{
if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){
fprintf(stderr, "Subscribe error: %s\n", mosquitto_reason_string(granted_qos[0]));
}else{
fprintf(stderr, "Subscribe error: Subscription refused.\n");
}
run = 0;
mosquitto_disconnect_v5(mosq, 0, NULL);
}
}else{
run = 0;
mosquitto_disconnect_v5(mosq, 0, NULL);
}
}
static void on_connect(struct mosquitto *mosq, void *obj, int reason_code, int flags, const mosquitto_property *properties)
{
struct mosq_ctrl *ctrl = obj;
if(reason_code == 0){
if(ctrl->response_topic){
mosquitto_subscribe(mosq, NULL, ctrl->response_topic, ctrl->cfg.qos);
free(ctrl->response_topic);
ctrl->response_topic = NULL;
}
}else{
if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){
if(reason_code == MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION){
fprintf(stderr, "Connection error: %s. Try connecting to an MQTT v5 broker, or use MQTT v3.x mode.\n", mosquitto_reason_string(reason_code));
}else{
fprintf(stderr, "Connection error: %s\n", mosquitto_reason_string(reason_code));
}
}else{
fprintf(stderr, "Connection error: %s\n", mosquitto_connack_string(reason_code));
}
run = 0;
mosquitto_disconnect_v5(mosq, 0, NULL);
}
}
int client_request_response(struct mosq_ctrl *ctrl)
{
struct mosquitto *mosq;
int rc;
time_t start;
mosquitto_lib_init();
mosq = mosquitto_new(ctrl->cfg.id, true, ctrl);
rc = client_opts_set(mosq, &ctrl->cfg);
if(rc) goto cleanup;
mosquitto_connect_v5_callback_set(mosq, on_connect);
mosquitto_subscribe_v5_callback_set(mosq, on_subscribe);
mosquitto_publish_v5_callback_set(mosq, on_publish);
mosquitto_message_v5_callback_set(mosq, on_message);
rc = client_connect(mosq, &ctrl->cfg);
if(rc) goto cleanup;
start = time(NULL);
while(run && start+10 > time(NULL)){
mosquitto_loop(mosq, -1, 1);
}
cleanup:
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
return rc;
}
File diff suppressed because it is too large Load Diff
+163
View File
@@ -0,0 +1,163 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include <cJSON.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
#include "password_mosq.h"
int dynsec_client__create(int argc, char *argv[], cJSON *j_command)
{
char *username = NULL, *password = NULL;
if(argc == 1){
username = argv[0];
}else if(argc == 2){
username = argv[0];
password = argv[1];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "createClient") == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|| (password && cJSON_AddStringToObject(j_command, "password", password) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_client__delete(int argc, char *argv[], cJSON *j_command)
{
char *username = NULL;
if(argc == 1){
username = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "deleteClient") == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command)
{
char *username = NULL, *password = NULL;
if(argc == 2){
username = argv[0];
password = argv[1];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "setClientPassword") == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|| cJSON_AddStringToObject(j_command, "password", password) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_client__get(int argc, char *argv[], cJSON *j_command)
{
char *username = NULL;
if(argc == 1){
username = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "getClient") == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command)
{
char *username = NULL, *rolename = NULL;
int priority = -1;
if(argc == 2){
username = argv[0];
rolename = argv[1];
}else if(argc == 3){
username = argv[0];
rolename = argv[1];
priority = atoi(argv[2]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_client__list_all(int argc, char *argv[], cJSON *j_command)
{
int count = -1, offset = -1;
if(argc == 0){
/* All clients */
}else if(argc == 1){
count = atoi(argv[0]);
}else if(argc == 2){
count = atoi(argv[0]);
offset = atoi(argv[1]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "listClients") == NULL
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
+185
View File
@@ -0,0 +1,185 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include <cJSON.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
#include "password_mosq.h"
int dynsec_group__create(int argc, char *argv[], cJSON *j_command)
{
char *groupname = NULL;
if(argc == 1){
groupname = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "createGroup") == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__delete(int argc, char *argv[], cJSON *j_command)
{
char *groupname = NULL;
if(argc == 1){
groupname = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "deleteGroup") == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command)
{
char *groupname = NULL;
if(argc == 1){
groupname = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "setAnonymousGroup") == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__get(int argc, char *argv[], cJSON *j_command)
{
char *groupname = NULL;
if(argc == 1){
groupname = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "getGroup") == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command)
{
char *groupname = NULL, *rolename = NULL;
int priority = -1;
if(argc == 2){
groupname = argv[0];
rolename = argv[1];
}else if(argc == 3){
groupname = argv[0];
rolename = argv[1];
priority = atoi(argv[2]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command)
{
int count = -1, offset = -1;
if(argc == 0){
/* All groups */
}else if(argc == 1){
count = atoi(argv[0]);
}else if(argc == 2){
count = atoi(argv[0]);
offset = atoi(argv[1]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "listGroups") == NULL
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command)
{
char *username, *groupname;
int priority = -1;
if(argc == 2){
username = argv[0];
groupname = argv[1];
}else if(argc == 3){
username = argv[0];
groupname = argv[1];
priority = atoi(argv[2]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", command) == NULL
|| cJSON_AddStringToObject(j_command, "username", username) == NULL
|| cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
+191
View File
@@ -0,0 +1,191 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include <cJSON.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
#include "password_mosq.h"
int dynsec_role__create(int argc, char *argv[], cJSON *j_command)
{
char *rolename = NULL;
if(argc == 1){
rolename = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "createRole") == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_role__delete(int argc, char *argv[], cJSON *j_command)
{
char *rolename = NULL;
if(argc == 1){
rolename = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "deleteRole") == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_role__get(int argc, char *argv[], cJSON *j_command)
{
char *rolename = NULL;
if(argc == 1){
rolename = argv[0];
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "getRole") == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command)
{
int count = -1, offset = -1;
if(argc == 0){
/* All roles */
}else if(argc == 1){
count = atoi(argv[0]);
}else if(argc == 2){
count = atoi(argv[0]);
offset = atoi(argv[1]);
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "listRoles") == NULL
|| (count > 0 && cJSON_AddNumberToObject(j_command, "count", count) == NULL)
|| (offset > 0 && cJSON_AddNumberToObject(j_command, "offset", offset) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command)
{
char *rolename, *acltype, *topic, *action;
bool allow;
int priority = -1;
if(argc == 5){
rolename = argv[0];
acltype = argv[1];
topic = argv[2];
action = argv[3];
priority = atoi(argv[4]);
}else if(argc == 4){
rolename = argv[0];
acltype = argv[1];
topic = argv[2];
action = argv[3];
}else{
return MOSQ_ERR_INVAL;
}
if(strcasecmp(acltype, "publishClientToBroker")
&& strcasecmp(acltype, "publishBrokerToClient")
&& strcasecmp(acltype, "subscribe")
&& strcasecmp(acltype, "unsubscribe")){
return MOSQ_ERR_INVAL;
}
if(!strcasecmp(action, "allow")){
allow = true;
}else if(!strcasecmp(action, "deny")){
allow = false;
}else{
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "addACLToRole") == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|| cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL
|| cJSON_AddStringToObject(j_command, "topic", topic) == NULL
|| cJSON_AddBoolToObject(j_command, "allow", allow) == NULL
|| (priority != -1 && cJSON_AddNumberToObject(j_command, "priority", priority) == NULL)
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command)
{
char *rolename, *acltype, *topic;
if(argc == 3){
rolename = argv[0];
acltype = argv[1];
topic = argv[2];
}else{
return MOSQ_ERR_INVAL;
}
if(strcasecmp(acltype, "publishClientToBroker")
&& strcasecmp(acltype, "publishBrokerToClient")
&& strcasecmp(acltype, "subscribe")
&& strcasecmp(acltype, "unsubscribe")){
return MOSQ_ERR_INVAL;
}
if(cJSON_AddStringToObject(j_command, "command", "removeACLFromRole") == NULL
|| cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL
|| cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL
|| cJSON_AddStringToObject(j_command, "topic", topic) == NULL
){
return MOSQ_ERR_NOMEM;
}else{
return MOSQ_ERR_SUCCESS;
}
}
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
void print_version(void)
{
int major, minor, revision;
mosquitto_lib_version(&major, &minor, &revision);
printf("mosquitto_ctrl version %s running on libmosquitto %d.%d.%d.\n", VERSION, major, minor, revision);
}
void print_usage(void)
{
printf("mosquitto_ctrl is a tool for administering certain Mosquitto features.\n");
print_version();
printf("\nGeneral usage: mosquitto_ctrl <module> <module-command> <command-options>\n");
printf("For module specific help use: mosquitto_ctrl <module> help\n");
printf("\nModules available: dynsec\n");
printf("\nSee https://mosquitto.org/man/mosquitto_ctrl-1.html for more information.\n\n");
}
int main(int argc, char *argv[])
{
struct mosq_ctrl ctrl;
int rc;
if(argc == 1){
print_usage();
return 1;
}
memset(&ctrl, 0, sizeof(ctrl));
init_config(&ctrl.cfg);
/* Shift program name out of args */
argc--;
argv++;
ctrl_config_parse(&ctrl.cfg, &argc, &argv);
if(argc < 2){
print_usage();
return 1;
}
if(!strcasecmp(argv[0], "dynsec")){
rc = dynsec__main(argc-1, &argv[1], &ctrl);
if(rc < 0){
/* Usage print */
rc = 0;
}else if(rc == MOSQ_ERR_SUCCESS){
rc = client_request_response(&ctrl);
}else if(rc == MOSQ_ERR_UNKNOWN){
/* Message printed already */
}else{
fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc));
}
}else{
fprintf(stderr, "Error: Module '%s' not supported.\n", argv[0]);
rc = MOSQ_ERR_NOT_SUPPORTED;
}
client_config_cleanup(&ctrl.cfg);
return rc;
}
+109
View File
@@ -0,0 +1,109 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#ifndef MOSQUITTO_CTRL_H
#define MOSQUITTO_CTRL_H
#include <cJSON.h>
#include <stdbool.h>
#include "mosquitto.h"
#define PORT_UNDEFINED -1
#define PORT_UNIX 0
struct mosq_config {
char *id;
int protocol_version;
int keepalive;
char *host;
int port;
int qos;
char *bind_address;
bool debug;
bool quiet;
char *username;
char *password;
char *options_file;
#ifdef WITH_TLS
char *cafile;
char *capath;
char *certfile;
char *keyfile;
char *ciphers;
bool insecure;
char *tls_alpn;
char *tls_version;
char *tls_engine;
char *tls_engine_kpass_sha1;
char *keyform;
# ifdef FINAL_WITH_TLS_PSK
char *psk;
char *psk_identity;
# endif
#endif
bool verbose; /* sub */
unsigned int timeout; /* sub */
#ifdef WITH_SOCKS
char *socks5_host;
int socks5_port;
char *socks5_username;
char *socks5_password;
#endif
};
struct mosq_ctrl {
struct mosq_config cfg;
char *request_topic;
char *response_topic;
cJSON *j_tree;
void (*payload_callback)(struct mosq_ctrl *, long , const void *);
};
void init_config(struct mosq_config *cfg);
int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[]);
int client_config_load(struct mosq_config *cfg);
void client_config_cleanup(struct mosq_config *cfg);
int client_request_response(struct mosq_ctrl *ctrl);
int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg);
int client_connect(struct mosquitto *mosq, struct mosq_config *cfg);
void dynsec__print_usage(void);
int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl);
int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command);
int dynsec_client__create(int argc, char *argv[], cJSON *j_command);
int dynsec_client__delete(int argc, char *argv[], cJSON *j_command);
int dynsec_client__get(int argc, char *argv[], cJSON *j_command);
int dynsec_client__list_all(int argc, char *argv[], cJSON *j_command);
int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command);
int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command);
int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command);
int dynsec_group__create(int argc, char *argv[], cJSON *j_command);
int dynsec_group__delete(int argc, char *argv[], cJSON *j_command);
int dynsec_group__get(int argc, char *argv[], cJSON *j_command);
int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command);
int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command);
int dynsec_role__create(int argc, char *argv[], cJSON *j_command);
int dynsec_role__delete(int argc, char *argv[], cJSON *j_command);
int dynsec_role__get(int argc, char *argv[], cJSON *j_command);
int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command);
int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command);
int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command);
#endif
File diff suppressed because it is too large Load Diff
-4
View File
@@ -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
View File
@@ -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
+1
View File
@@ -12,6 +12,7 @@ FIND_PATH(
HINTS
CJSON_DIR
/usr/include/cjson
/usr/local/include/cjson
)
FIND_LIBRARY( CJSON_LIBRARY
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
+6
View File
@@ -511,6 +511,12 @@
<manvolnum>5</manvolnum>
</citerefentry>
</member>
<member>
<citerefentry>
<refentrytitle><link xlink:href="mosquitto_ctrl-1.html">mosquitto_ctrl</link></refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>
</member>
<member>
<citerefentry>
<refentrytitle><link xlink:href="mosquitto_passwd-1.html">mosquitto_passwd</link></refentrytitle>
+5
View File
@@ -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
View File
@@ -1,2 +1,3 @@
add_subdirectory(dynamic-security)
add_subdirectory(message-timestamp)
add_subdirectory(payload-modification)
+1
View File
@@ -1,4 +1,5 @@
DIRS= \
dynamic-security \
message-timestamp \
payload-modification
+6 -1
View File
@@ -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.
+36
View File
@@ -0,0 +1,36 @@
set( CLIENT_INC ${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/include
${STDBOOL_H_PATH} ${STDINT_H_PATH} ${PTHREAD_INCLUDE_DIR}
${OPENSSL_INCLUDE_DIR})
set( CLIENT_DIR ${mosquitto_BINARY_DIR}/lib)
if (CJSON_FOUND)
set( CLIENT_DIR "${CLIENT_DIR} ${CJSON_DIR}" )
set( CLIENT_INC "${CLIENT_INC};${CJSON_INCLUDE_DIRS}" )
endif()
include_directories(${CLIENT_INC})
link_directories(${CLIENT_DIR})
add_library(mosquitto_dynamic_security SHARED
acl.c
auth.c
clients.c
dynamic_security.h
groups.c
json_help.c
json_help.h
plugin.c
roles.c
sub_matches_sub.c)
set_target_properties(mosquitto_dynamic_security PROPERTIES
POSITION_INDEPENDENT_CODE 1
)
set_target_properties(mosquitto_dynamic_security PROPERTIES PREFIX "")
if (CJSON_FOUND)
target_link_libraries(mosquitto_dynamic_security ${CJSON_LIBRARIES})
endif()
install(TARGETS mosquitto_dynamic_security RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+60
View File
@@ -0,0 +1,60 @@
include ../../config.mk
.PHONY : all binary check clean reallyclean test install uninstall
PLUGIN_NAME=mosquitto_dynamic_security
LOCAL_CPPFLAGS=-I/usr/include/cjson -I/usr/local/include/cjson -I../../src/
OBJS= \
acl.o \
auth.o \
clients.o \
groups.o \
json_help.o \
plugin.o \
roles.o \
sub_matches_sub.o
all : binary
binary : ${PLUGIN_NAME}.so
${PLUGIN_NAME}.so : ${OBJS}
${CROSS_COMPILE}${CC} $(PLUGIN_LDFLAGS) -fPIC -shared $^ -o $@ -lcjson
acl.o : acl.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
auth.o : auth.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
clients.o : clients.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
groups.o : groups.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
json_help.o : json_help.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
plugin.o : plugin.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
roles.o : roles.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
sub_matches_sub.o : sub_matches_sub.c dynamic_security.h
${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@
reallyclean : clean
clean:
-rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno
check: test
test:
install: ${PLUGIN_NAME}.so
$(INSTALL) -d "${DESTDIR}$(prefix)/lib"
$(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${prefix}/lib/${PLUGIN_NAME}.so"
uninstall :
-rm -f "${DESTDIR}${prefix}/lib/${PLUGIN_NAME}.so"
File diff suppressed because it is too large Load Diff
+248
View File
@@ -0,0 +1,248 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include "dynamic_security.h"
#include "mosquitto.h"
#include "mosquitto_broker.h"
#include "mosquitto_plugin.h"
typedef int (*MOSQ_FUNC_acl_check)(struct mosquitto_evt_acl_check *, struct dynsec__rolelist *);
/* FIXME - CACHE! */
/* ################################################################
* #
* # ACL check - publish broker to client
* #
* ################################################################ */
static int acl_check_publish_b2c(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
{
struct dynsec__rolelist *rolelist, *rolelist_tmp;
struct dynsec__acl *acl, *acl_tmp;
bool result;
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_ITER(hh, rolelist->role->acls.publish_b2c, acl, acl_tmp){
mosquitto_topic_matches_sub(acl->topic, ed->topic, &result);
if(result){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
}
}
return MOSQ_ERR_NOT_FOUND;
}
/* ################################################################
* #
* # ACL check - publish client to broker
* #
* ################################################################ */
static int acl_check_publish_c2b(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
{
struct dynsec__rolelist *rolelist, *rolelist_tmp;
struct dynsec__acl *acl, *acl_tmp;
bool result;
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_ITER(hh, rolelist->role->acls.publish_c2b, acl, acl_tmp){
mosquitto_topic_matches_sub(acl->topic, ed->topic, &result);
if(result){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
}
}
return MOSQ_ERR_NOT_FOUND;
}
/* ################################################################
* #
* # ACL check - subscribe
* #
* ################################################################ */
static int acl_check_subscribe(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
{
struct dynsec__rolelist *rolelist, *rolelist_tmp;
struct dynsec__acl *acl, *acl_tmp;
int len;
len = strlen(ed->topic);
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, acl);
if(acl){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
HASH_ITER(hh, rolelist->role->acls.subscribe_pattern, acl, acl_tmp){
if(sub_acl_check(acl->topic, ed->topic)){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
}
}
return MOSQ_ERR_NOT_FOUND;
}
/* ################################################################
* #
* # ACL check - unsubscribe
* #
* ################################################################ */
static int acl_check_unsubscribe(struct mosquitto_evt_acl_check *ed, struct dynsec__rolelist *base_rolelist)
{
struct dynsec__rolelist *rolelist, *rolelist_tmp;
struct dynsec__acl *acl, *acl_tmp;
int len;
len = strlen(ed->topic);
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, acl);
if(acl){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
HASH_ITER(hh, rolelist->role->acls.unsubscribe_pattern, acl, acl_tmp){
if(sub_acl_check(acl->topic, ed->topic)){
if(acl->allow){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_ACL_DENIED;
}
}
}
}
return MOSQ_ERR_NOT_FOUND;
}
/* ################################################################
* #
* # ACL check - generic check
* #
* ################################################################ */
static int acl_check(struct mosquitto_evt_acl_check *ed, MOSQ_FUNC_acl_check check, bool default_access)
{
struct dynsec__client *client;
struct dynsec__grouplist *grouplist, *grouplist_tmp;
const char *username;
int rc;
username = mosquitto_client_username(ed->client);
if(username){
client = dynsec_clients__find(username);
if(client == NULL) return MOSQ_ERR_PLUGIN_DEFER;
/* Client roles */
rc = check(ed, client->rolelist);
if(rc != MOSQ_ERR_NOT_FOUND){
return rc;
}
HASH_ITER(hh, client->grouplist, grouplist, grouplist_tmp){
rc = check(ed, grouplist->group->rolelist);
if(rc != MOSQ_ERR_NOT_FOUND){
return rc;
}
}
}else if(dynsec_anonymous_group){
/* If we have a group for anonymous users, use that for checking. */
rc = check(ed, dynsec_anonymous_group->rolelist);
if(rc != MOSQ_ERR_NOT_FOUND){
return rc;
}
}
if(default_access == false){
return MOSQ_ERR_PLUGIN_DEFER;
}else{
if(!strncmp(ed->topic, "$CONTROL", strlen("$CONTROL"))){
/* We never give fall through access to $CONTROL topics, they must
* be granted explicitly. */
return MOSQ_ERR_PLUGIN_DEFER;
}else{
return MOSQ_ERR_SUCCESS;
}
}
}
/* ################################################################
* #
* # ACL check - plugin callback
* #
* ################################################################ */
int dynsec__acl_check_callback(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_acl_check *ed = event_data;
/* ACL checks are made in the order below until a match occurs, at which
* point the decision is made.
*
* User roles in priority order highest to lowest.
* Roles have their ACLs checked in priority order, highest to lowest
* Groups are processed in priority order highest to lowest
* Group roles are processed in priority order, highest to lowest
* Roles have their ACLs checked in priority order, highest to lowest
*/
switch(ed->access){
case MOSQ_ACL_SUBSCRIBE:
return acl_check(event_data, acl_check_subscribe, default_access.subscribe);
break;
case MOSQ_ACL_UNSUBSCRIBE:
return acl_check(event_data, acl_check_unsubscribe, default_access.unsubscribe);
break;
case MOSQ_ACL_WRITE: /* Client to broker */
return acl_check(event_data, acl_check_publish_c2b, default_access.publish_c2b);
break;
case MOSQ_ACL_READ:
return acl_check(event_data, acl_check_publish_b2c, default_access.publish_b2c);
break;
default:
return MOSQ_ERR_PLUGIN_DEFER;
}
return MOSQ_ERR_PLUGIN_DEFER;
}
+193
View File
@@ -0,0 +1,193 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include "dynamic_security.h"
#include "mosquitto.h"
#include "mosquitto_broker.h"
/* ################################################################
* #
* # Base64 encoding/decoding
* #
* ################################################################ */
int dynsec_auth__base64_encode(unsigned char *in, unsigned int in_len, char **encoded)
{
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, in, in_len);
if(BIO_flush(b64) != 1){
BIO_free_all(b64);
return 1;
}
BIO_get_mem_ptr(b64, &bptr);
*encoded = mosquitto_malloc(bptr->length+1);
if(!(*encoded)){
BIO_free_all(b64);
return 1;
}
memcpy(*encoded, bptr->data, bptr->length);
(*encoded)[bptr->length] = '\0';
BIO_free_all(b64);
return 0;
}
int dynsec_auth__base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len)
{
BIO *bmem, *b64;
int slen;
slen = strlen(in);
b64 = BIO_new(BIO_f_base64());
if(!b64){
return 1;
}
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
if(!bmem){
BIO_free_all(b64);
return 1;
}
b64 = BIO_push(b64, bmem);
BIO_write(bmem, in, slen);
if(BIO_flush(bmem) != 1){
BIO_free_all(b64);
return 1;
}
*decoded = mosquitto_calloc(slen, 1);
if(!(*decoded)){
BIO_free_all(b64);
return 1;
}
*decoded_len = BIO_read(b64, *decoded, slen);
BIO_free_all(b64);
if(*decoded_len <= 0){
mosquitto_free(*decoded);
*decoded = NULL;
*decoded_len = 0;
return 1;
}
return 0;
}
/* ################################################################
* #
* # Password functions
* #
* ################################################################ */
int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password)
{
const EVP_MD *digest;
int iterations;
if(new_password){
if(RAND_bytes(client->pw.salt, sizeof(client->pw.salt)) != 1){
return MOSQ_ERR_UNKNOWN;
}
iterations = PW_DEFAULT_ITERATIONS;
}else{
iterations = client->pw.iterations;
}
if(iterations < 1){
return MOSQ_ERR_INVAL;
}
client->pw.iterations = iterations;
digest = EVP_get_digestbyname("sha512");
if(!digest){
return MOSQ_ERR_UNKNOWN;
}
return !PKCS5_PBKDF2_HMAC(password, strlen(password),
client->pw.salt, sizeof(client->pw.salt), iterations,
digest, password_hash_len, password_hash);
}
/* ################################################################
* #
* # Username/password check
* #
* ################################################################ */
static int memcmp_const(const void *a, const void *b, size_t len)
{
size_t i;
int rc = 0;
if(!a || !b) return 1;
for(i=0; i<len; i++){
if( ((char *)a)[i] != ((char *)b)[i] ){
rc = 1;
}
}
return rc;
}
int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_basic_auth *ed = event_data;
struct dynsec__client *client;
unsigned char password_hash[64]; /* For SHA512 */
const char *clientid;
if(ed->username == NULL || ed->password == NULL) return MOSQ_ERR_PLUGIN_DEFER;
client = dynsec_clients__find(ed->username);
if(client){
if(client->clientid){
clientid = mosquitto_client_id(ed->client);
if(clientid == NULL || strcmp(client->clientid, clientid)){
return MOSQ_ERR_AUTH;
}
}
if(dynsec_auth__pw_hash(client, ed->password, password_hash, sizeof(password_hash), false) == MOSQ_ERR_SUCCESS){
if(memcmp_const(client->pw.password_hash, password_hash, sizeof(password_hash)) == 0){
return MOSQ_ERR_SUCCESS;
}else{
return MOSQ_ERR_AUTH;
}
}else{
return MOSQ_ERR_PLUGIN_DEFER;
}
}else{
return MOSQ_ERR_PLUGIN_DEFER;
}
}
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
#ifndef DYNAMIC_SECURITY_H
#define DYNAMIC_SECURITY_H
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include <cJSON.h>
#include <uthash.h>
#include "mosquitto.h"
#include "password_mosq.h"
/* ################################################################
* #
* # Error codes
* #
* ################################################################ */
#define ERR_USER_NOT_FOUND 10000
#define ERR_GROUP_NOT_FOUND 10001
#define ERR_LIST_NOT_FOUND 10002
/* ################################################################
* #
* # Datatypes
* #
* ################################################################ */
struct dynsec__clientlist{
UT_hash_handle hh;
char *username;
struct dynsec__client *client;
int priority;
};
struct dynsec__grouplist{
UT_hash_handle hh;
char *groupname;
struct dynsec__group *group;
int priority;
};
struct dynsec__rolelist{
UT_hash_handle hh;
char *rolename;
struct dynsec__role *role;
int priority;
};
struct dynsec__client{
UT_hash_handle hh;
struct mosquitto_pw pw;
struct dynsec__rolelist *rolelist;
struct dynsec__grouplist *grouplist;
char *username;
char *clientid;
char *text_name;
char *text_description;
};
struct dynsec__group{
UT_hash_handle hh;
struct dynsec__rolelist *rolelist;
struct dynsec__clientlist *clientlist;
char *groupname;
char *text_name;
char *text_description;
};
struct dynsec__acl{
UT_hash_handle hh;
char *topic;
int priority;
bool allow;
};
struct dynsec__acls{
struct dynsec__acl *publish_c2b;
struct dynsec__acl *publish_b2c;
struct dynsec__acl *subscribe_literal;
struct dynsec__acl *subscribe_pattern;
struct dynsec__acl *unsubscribe_literal;
struct dynsec__acl *unsubscribe_pattern;
};
struct dynsec__role{
UT_hash_handle hh;
struct dynsec__acls acls;
char *rolename;
char *text_name;
char *text_description;
};
struct dynsec__acl_default_access{
bool publish_c2b;
bool publish_b2c;
bool subscribe;
bool unsubscribe;
};
extern struct dynsec__group *dynsec_anonymous_group;
extern struct dynsec__acl_default_access default_access;
/* ################################################################
* #
* # Plugin Functions
* #
* ################################################################ */
void dynsec__config_save(void);
int dynsec__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON *commands);
void dynsec__command_reply(cJSON *j_responses, struct mosquitto *context, const char *command, const char *error, const char *correlation_data);
/* ################################################################
* #
* # ACL Functions
* #
* ################################################################ */
int dynsec__acl_check_callback(int event, void *event_data, void *userdata);
bool sub_acl_check(const char *acl, const char *sub);
/* ################################################################
* #
* # Auth Functions
* #
* ################################################################ */
int dynsec_auth__base64_encode(unsigned char *in, unsigned int in_len, char **encoded);
int dynsec_auth__base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len);
int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password);
int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata);
/* ################################################################
* #
* # Client Functions
* #
* ################################################################ */
void dynsec_clients__cleanup(void);
int dynsec_clients__config_load(cJSON *tree);
int dynsec_clients__config_save(cJSON *tree);
int dynsec_clients__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_clients__process_set_password(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
struct dynsec__client *dynsec_clients__find(const char *username);
void dynsec_clients__remove_role_from_all(const struct dynsec__role *role);
cJSON *dynsec_clientlists__all_to_json(struct dynsec__clientlist *base_clientlist);
/* ################################################################
* #
* # Group Functions
* #
* ################################################################ */
void dynsec_groups__cleanup(void);
int dynsec_groups__config_load(cJSON *tree);
int dynsec_groups__add_client(const char *username, const char *groupname, int priority, bool update_config);
int dynsec_groups__config_save(cJSON *tree);
int dynsec_groups__process_add_client(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_add_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_remove_client(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__process_set_anonymous_group(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_groups__remove_client(const char *username, const char *groupname, bool update_config);
struct dynsec__group *dynsec_groups__find(const char *groupname);
void dynsec_groups__remove_role_from_all(const struct dynsec__role *role);
cJSON *dynsec_grouplists__all_to_json(struct dynsec__grouplist *base_grouplist);
/* ################################################################
* #
* # Role Functions
* #
* ################################################################ */
void dynsec_roles__cleanup(void);
int dynsec_roles__config_load(cJSON *tree);
int dynsec_roles__config_save(cJSON *tree);
int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_create(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_delete(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_get(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_list(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data);
struct dynsec__role *dynsec_roles__find(const char *rolename);
int dynsec_rolelists__add_role(struct dynsec__rolelist **base_rolelist, struct dynsec__role *role, int priority);
int dynsec_rolelists__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist);
int dynsec_rolelists__remove_role(struct dynsec__rolelist **base_rolelist, const struct dynsec__role *role);
void dynsec_rolelists__free_all(struct dynsec__rolelist **base_rolelist);
cJSON *dynsec_rolelists__all_to_json(struct dynsec__rolelist *base_rolelist);
#endif
File diff suppressed because it is too large Load Diff
@@ -18,8 +18,9 @@ Contributors:
#include <cJSON.h>
#include <stdbool.h>
#include <stdlib.h>
#include "mosquitto_broker_internal.h"
#include "mosquitto.h"
int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value)
@@ -30,7 +31,7 @@ int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, boo
*value = default_value;
}
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
jtmp = cJSON_GetObjectItem(json, name);
if(jtmp){
if(cJSON_IsBool(jtmp) == false){
return MOSQ_ERR_INVAL;
@@ -53,7 +54,7 @@ int json_get_int(cJSON *json, const char *name, int *value, bool optional, int d
*value = default_value;
}
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
jtmp = cJSON_GetObjectItem(json, name);
if(jtmp){
if(cJSON_IsNumber(jtmp) == false){
return MOSQ_ERR_INVAL;
@@ -74,7 +75,7 @@ int json_get_string(cJSON *json, const char *name, char **value, bool optional)
*value = NULL;
jtmp = cJSON_GetObjectItemCaseSensitive(json, name);
jtmp = cJSON_GetObjectItem(json, name);
if(jtmp){
if(cJSON_IsString(jtmp) == false){
return MOSQ_ERR_INVAL;
+485
View File
@@ -0,0 +1,485 @@
/*
Copyright (c) 2020 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include <cJSON.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "json_help.h"
#include "mosquitto.h"
#include "mosquitto_broker.h"
#include "mosquitto_plugin.h"
#include "dynamic_security.h"
static mosquitto_plugin_id_t *plg_id = NULL;
static char *config_file = NULL;
struct dynsec__acl_default_access default_access = {false, false, false, false};
void dynsec__command_reply(cJSON *j_responses, struct mosquitto *context, const char *command, const char *error, const char *correlation_data)
{
cJSON *j_response;
j_response = cJSON_CreateObject();
if(j_response == NULL) return;
if(cJSON_AddStringToObject(j_response, "command", command) == NULL
|| (error && cJSON_AddStringToObject(j_response, "error", error) == NULL)
|| (correlation_data && cJSON_AddStringToObject(j_response, "correlationData", correlation_data) == NULL)
){
cJSON_Delete(j_response);
return;
}
cJSON_AddItemToArray(j_responses, j_response);
}
static void send_response(cJSON *tree)
{
char *payload;
payload = cJSON_PrintUnformatted(tree);
cJSON_Delete(tree);
if(payload == NULL) return;
mosquitto_broker_publish(NULL, "$CONTROL/dynamic-security/v1/response",
strlen(payload), payload, 0, 0, NULL);
}
static int dynsec_control_callback(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_control *ed = event_data;
cJSON *tree, *commands;
cJSON *j_response_tree, *j_responses;
/* Create object for responses */
j_response_tree = cJSON_CreateObject();
if(j_response_tree == NULL){
return MOSQ_ERR_NOMEM;
}
j_responses = cJSON_CreateArray();
if(j_responses == NULL){
cJSON_Delete(j_response_tree);
return MOSQ_ERR_NOMEM;
}
cJSON_AddItemToObject(j_response_tree, "responses", j_responses);
/* Parse cJSON tree */
tree = cJSON_ParseWithLength(ed->payload, ed->payloadlen);
if(tree == NULL){
dynsec__command_reply(j_responses, ed->client, "Unknown command", "Payload not valid JSON", NULL);
send_response(j_response_tree);
return MOSQ_ERR_SUCCESS;
}
commands = cJSON_GetObjectItem(tree, "commands");
if(commands == NULL || !cJSON_IsArray(commands)){
cJSON_Delete(tree);
dynsec__command_reply(j_responses, ed->client, "Unknown command", "Invalid/missing commands", NULL);
send_response(j_response_tree);
return MOSQ_ERR_SUCCESS;
}
/* Handle commands */
dynsec__handle_control(j_responses, ed->client, commands);
cJSON_Delete(tree);
send_response(j_response_tree);
return MOSQ_ERR_SUCCESS;
}
int dynsec__process_default_acl_access(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
{
cJSON *j_actions, *j_action, *j_acltype, *j_allow;
bool allow;
j_actions = cJSON_GetObjectItem(command, "acls");
if(j_actions == NULL || !cJSON_IsArray(j_actions)){
dynsec__command_reply(j_responses, context, "setDefaultACLAccess", "Missing/invalid actions array", correlation_data);
return MOSQ_ERR_INVAL;
}
cJSON_ArrayForEach(j_action, j_actions){
j_acltype = cJSON_GetObjectItem(j_action, "acltype");
j_allow = cJSON_GetObjectItem(j_action, "allow");
if(j_acltype && cJSON_IsString(j_acltype)
&& j_allow && cJSON_IsBool(j_allow)){
allow = cJSON_IsTrue(j_allow);
if(!strcasecmp(j_acltype->valuestring, "publishClientToBroker")){
default_access.publish_c2b = allow;
}else if(!strcasecmp(j_acltype->valuestring, "publishBrokerToClient")){
default_access.publish_b2c = allow;
}else if(!strcasecmp(j_acltype->valuestring, "subscribe")){
default_access.subscribe = allow;
}else if(!strcasecmp(j_acltype->valuestring, "unsubscribe")){
default_access.unsubscribe = allow;
}
}
}
dynsec__config_save();
dynsec__command_reply(j_responses, context, "setDefaultACLAccess", NULL, correlation_data);
return MOSQ_ERR_SUCCESS;
}
int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
{
int i;
for(i=0; i<supported_version_count; i++){
if(supported_versions[i] == 5){
return 5;
}
}
return -1;
}
static int dynsec__general_config_load(cJSON *tree)
{
cJSON *j_default_access, *jtmp;
j_default_access = cJSON_GetObjectItem(tree, "defaultACLAccess");
if(j_default_access && cJSON_IsObject(j_default_access)){
jtmp = cJSON_GetObjectItem(j_default_access, "publishClientToBroker");
if(jtmp && cJSON_IsBool(jtmp)){
default_access.publish_c2b = cJSON_IsTrue(jtmp);
}else{
default_access.publish_c2b = false;
}
jtmp = cJSON_GetObjectItem(j_default_access, "publishBrokerToClient");
if(jtmp && cJSON_IsBool(jtmp)){
default_access.publish_b2c = cJSON_IsTrue(jtmp);
}else{
default_access.publish_b2c = false;
}
jtmp = cJSON_GetObjectItem(j_default_access, "subscribe");
if(jtmp && cJSON_IsBool(jtmp)){
default_access.subscribe = cJSON_IsTrue(jtmp);
}else{
default_access.subscribe = false;
}
jtmp = cJSON_GetObjectItem(j_default_access, "unsubscribe");
if(jtmp && cJSON_IsBool(jtmp)){
default_access.unsubscribe = cJSON_IsTrue(jtmp);
}else{
default_access.unsubscribe = false;
}
}
return MOSQ_ERR_SUCCESS;
}
static int dynsec__general_config_save(cJSON *tree)
{
cJSON *j_default_access;
j_default_access = cJSON_CreateObject();
if(j_default_access == NULL){
return 1;
}
cJSON_AddItemToObject(tree, "defaultACLAccess", j_default_access);
if(cJSON_AddBoolToObject(j_default_access, "publishClientToBroker", default_access.publish_c2b) == NULL
|| cJSON_AddBoolToObject(j_default_access, "publishBrokerToClient", default_access.publish_b2c) == NULL
|| cJSON_AddBoolToObject(j_default_access, "subscribe", default_access.subscribe) == NULL
|| cJSON_AddBoolToObject(j_default_access, "unsubscribe", default_access.unsubscribe) == NULL
){
return 1;
}
return MOSQ_ERR_SUCCESS;
}
static int dynsec__config_load(void)
{
FILE *fptr;
long flen;
char *json_str;
cJSON *tree;
/* Save to file */
fptr = fopen(config_file, "rt");
if(fptr == NULL){
return 1;
}
fseek(fptr, 0, SEEK_END);
flen = ftell(fptr);
fseek(fptr, 0, SEEK_SET);
json_str = mosquitto_calloc(flen+1, sizeof(char));
if(json_str == NULL){
fclose(fptr);
return 1;
}
if(fread(json_str, 1, flen, fptr) != flen){
fclose(fptr);
return 1;
}
fclose(fptr);
tree = cJSON_Parse(json_str);
mosquitto_free(json_str);
if(tree == NULL){
return 1;
}
if(dynsec__general_config_load(tree)){
cJSON_Delete(tree);
return 1;
}
if(dynsec_roles__config_load(tree)){
cJSON_Delete(tree);
return 1;
}
if(dynsec_clients__config_load(tree)){
cJSON_Delete(tree);
return 1;
}
if(dynsec_groups__config_load(tree)){
cJSON_Delete(tree);
return 1;
}
cJSON_Delete(tree);
return 0;
}
void dynsec__config_save(void)
{
cJSON *tree;
int file_path_len;
char *file_path;
FILE *fptr;
int json_str_len;
char *json_str;
tree = cJSON_CreateObject();
if(tree == NULL) return;
if(dynsec__general_config_save(tree)){
cJSON_Delete(tree);
return;
}
if(dynsec_clients__config_save(tree)){
cJSON_Delete(tree);
return;
}
if(dynsec_groups__config_save(tree)){
cJSON_Delete(tree);
return;
}
if(dynsec_roles__config_save(tree)){
cJSON_Delete(tree);
return;
}
/* Print json to string */
json_str = cJSON_Print(tree);
if(json_str == NULL){
cJSON_Delete(tree);
return;
}
cJSON_Delete(tree);
json_str_len = strlen(json_str);
/* Save to file */
file_path_len = strlen(config_file) + 1;
file_path = mosquitto_malloc(file_path_len);
if(file_path == NULL){
mosquitto_free(json_str);
return;
}
snprintf(file_path, file_path_len, "%s.new", config_file);
fptr = fopen(file_path, "wt");
if(fptr == NULL){
mosquitto_free(json_str);
mosquitto_free(file_path);
return;
}
fwrite(json_str, 1, json_str_len, fptr);
mosquitto_free(json_str);
fclose(fptr);
/* Everything is ok, so move new file over proper file */
rename(file_path, config_file);
mosquitto_free(file_path);
}
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *options, int option_count)
{
int i;
for(i=0; i<option_count; i++){
if(!strcasecmp(options[i].key, "config_file")){
config_file = mosquitto_strdup(options[i].value);
if(config_file == NULL){
return MOSQ_ERR_NOMEM;
}
break;
}
}
if(config_file == NULL){
mosquitto_log_printf(MOSQ_LOG_WARNING, "Warning: Dynamic security plugin has no plugin_opt_config_file defined. The plugin will not be activated.");
return MOSQ_ERR_SUCCESS;
}
plg_id = identifier;
dynsec__config_load();
mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", NULL);
mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, NULL);
mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, NULL);
return MOSQ_ERR_SUCCESS;
}
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *options, int option_count)
{
if(plg_id){
mosquitto_callback_unregister(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1");
mosquitto_callback_unregister(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL);
mosquitto_callback_unregister(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL);
}
dynsec_groups__cleanup();
dynsec_clients__cleanup();
dynsec_roles__cleanup();
mosquitto_free(config_file);
config_file = NULL;
return MOSQ_ERR_SUCCESS;
}
/* ################################################################
* #
* # $CONTROL/dynamic-security/v1 handler
* #
* ################################################################ */
int dynsec__handle_control(cJSON *j_responses, struct mosquitto *context, cJSON *commands)
{
int rc = MOSQ_ERR_SUCCESS;
cJSON *aiter;
char *command;
char *correlation_data = NULL;
cJSON_ArrayForEach(aiter, commands){
if(cJSON_IsObject(aiter)){
if(json_get_string(aiter, "command", &command, false) == MOSQ_ERR_SUCCESS){
if(json_get_string(aiter, "correlationData", &correlation_data, true) != MOSQ_ERR_SUCCESS){
dynsec__command_reply(j_responses, context, command, "Invalid correlationData data type.", NULL);
return MOSQ_ERR_INVAL;
}
/* Plugin */
if(!strcasecmp(command, "setDefaultACLAccess")){
rc = dynsec__process_default_acl_access(j_responses, context, aiter, correlation_data);
/* Clients */
}else if(!strcasecmp(command, "createClient")){
rc = dynsec_clients__process_create(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "deleteClient")){
rc = dynsec_clients__process_delete(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "getClient")){
rc = dynsec_clients__process_get(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "listClients")){
rc = dynsec_clients__process_list(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "modifyClient")){
rc = dynsec_clients__process_modify(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "setClientPassword")){
rc = dynsec_clients__process_set_password(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "addClientRole")){
rc = dynsec_clients__process_add_role(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "removeClientRole")){
rc = dynsec_clients__process_remove_role(j_responses, context, aiter, correlation_data);
/* Groups */
}else if(!strcasecmp(command, "addGroupClient")){
rc = dynsec_groups__process_add_client(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "createGroup")){
rc = dynsec_groups__process_create(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "deleteGroup")){
rc = dynsec_groups__process_delete(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "getGroup")){
rc = dynsec_groups__process_get(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "listGroups")){
rc = dynsec_groups__process_list(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "modifyGroup")){
rc = dynsec_groups__process_modify(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "removeGroupClient")){
rc = dynsec_groups__process_remove_client(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "addGroupRole")){
rc = dynsec_groups__process_add_role(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "removeGroupRole")){
rc = dynsec_groups__process_remove_role(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "setAnonymousGroup")){
rc = dynsec_groups__process_set_anonymous_group(j_responses, context, aiter, correlation_data);
/* Roles */
}else if(!strcasecmp(command, "createRole")){
rc = dynsec_roles__process_create(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "getRole")){
rc = dynsec_roles__process_get(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "listRoles")){
rc = dynsec_roles__process_list(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "modifyRole")){
rc = dynsec_roles__process_modify(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "deleteRole")){
rc = dynsec_roles__process_delete(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "addRoleACL")){
rc = dynsec_roles__process_add_acl(j_responses, context, aiter, correlation_data);
}else if(!strcasecmp(command, "removeRoleACL")){
rc = dynsec_roles__process_remove_acl(j_responses, context, aiter, correlation_data);
/* Unknown */
}else{
dynsec__command_reply(j_responses, context, command, "Unknown command", correlation_data);
rc = MOSQ_ERR_INVAL;
}
}else{
dynsec__command_reply(j_responses, context, "Unknown command", "Missing command", correlation_data);
rc = MOSQ_ERR_INVAL;
}
}else{
dynsec__command_reply(j_responses, context, "Unknown command", "Command not an object", correlation_data);
rc = MOSQ_ERR_INVAL;
}
}
return rc;
}
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *strtok_hier(char *str, char **saveptr)
{
char *c;
if(str != NULL){
*saveptr = str;
}
if(*saveptr == NULL){
return NULL;
}
c = strchr(*saveptr, '/');
if(c){
str = *saveptr;
*saveptr = c+1;
c[0] = '\0';
}else if(*saveptr){
/* No match, but surplus string */
str = *saveptr;
*saveptr = NULL;
}
return str;
}
static int count_hier_levels(const char *s)
{
int count = 1;
const char *c = s;
while((c = strchr(c, '/')) && c[0]){
c++;
count++;
}
return count;
}
static bool hash_check(char *s, size_t *len)
{
if((*len) == 1 && s[0] == '#'){
s[0] = '\0';
(*len)--;
return true;
}else if((*len) > 1 && s[(*len)-2] == '/' && s[(*len)-1] == '#'){
s[(*len)-2] = '\0';
s[(*len)-1] = '\0';
(*len) -= 2;
return true;
}
return false;
}
bool sub_acl_check(const char *acl, const char *sub)
{
char *acl_local;
char *sub_local;
size_t acl_len, sub_len;
bool acl_hash = false, sub_hash = false;
int acl_levels, sub_levels;
int i;
char *acl_token, *sub_token;
char *acl_saveptr, *sub_saveptr;
acl_len = strlen(acl);
if(acl_len == 1 && acl[0] == '#'){
return true;
}
sub_len = strlen(sub);
//mosquitto_validate_utf8(acl, acl_len);
acl_local = strdup(acl);
sub_local = strdup(sub);
if(acl_local == NULL || sub_local == NULL){
free(acl_local);
free(sub_local);
return false;
}
acl_hash = hash_check(acl_local, &acl_len);
sub_hash = hash_check(sub_local, &sub_len);
if(sub_hash == true && acl_hash == false){
free(acl_local);
free(sub_local);
return false;
}
acl_levels = count_hier_levels(acl_local);
sub_levels = count_hier_levels(sub_local);
if(acl_levels > sub_levels){
free(acl_local);
free(sub_local);
return false;
}else if(sub_levels > acl_levels){
if(acl_hash == false){
free(acl_local);
free(sub_local);
return false;
}
}
acl_saveptr = acl_local;
sub_saveptr = sub_local;
for(i=0; i<sub_levels; i++){
acl_token = strtok_hier(acl_saveptr, &acl_saveptr);
sub_token = strtok_hier(sub_saveptr, &sub_saveptr);
if(i<acl_levels &&
(!strcmp(acl_token, "+")
|| !strcmp(acl_token, sub_token))){
/* This level matches a single level wildcard, or is an exact
* match, so carry on checking. */
}else if(i>=acl_levels && acl_hash == true){
/* The sub has more levels of hierarchy than the acl, but the acl
* ends in a multi level wildcard so the match is fine. */
}else{
free(acl_local);
free(sub_local);
return false;
}
}
free(acl_local);
free(sub_local);
return true;
}
#ifdef TEST
#define BLK "\e[0;30m"
#define RED "\e[0;31m"
#define GRN "\e[0;32m"
#define YEL "\e[0;33m"
#define BLU "\e[0;34m"
#define MAG "\e[0;35m"
#define CYN "\e[0;36m"
#define WHT "\e[0;37m"
#define RST "\e[0m"
void hier_test(const char *s, int expected)
{
int levels;
levels = count_hier_levels(s);
printf("HIER %s %d:%d ", s, expected, levels);
if(levels == expected){
printf(GRN "passed" RST "\n");
}else{
printf(RED "failed" RST "\n");
}
}
void test(const char *sub1, const char *sub2, bool expected)
{
bool result;
printf("ACL %s : %s ", sub1, sub2);
result = sub_acl_check(sub1, sub2);
if(result == expected){
printf(GRN "passed\n" RST);
}else{
printf(RED "failed\n" RST);
}
}
int main(int argc, char *argv[])
{
hier_test("foo/+/bar", 3);
hier_test("foo/#", 2);
hier_test("foo/+/ba℞/#", 4);
hier_test("foo/baz/ba℞", 3);
hier_test("foo/+/ba℞/#", 4);
hier_test("foo/baz/ba℞/+", 4);
hier_test("foo/+/ba℞/#", 4);
hier_test("foo/baz/ba℞/#", 4);
hier_test("foo/+/ba℞/#", 4);
hier_test("foo/baz/+/#", 4);
hier_test("/+//#", 4);
hier_test("/foo///#", 5);
hier_test("#", 1);
hier_test("+", 1);
hier_test("/", 2);
hier_test("////////////////////////////////////////////////////////////////////////////////////////////////////", 101);
test("foo/+/bar", "foo/#", false);
test("foo/+/ba℞/#", "foo/baz/ba℞", true);
test("foo/+/ba℞/#", "foo/baz/ba℞/+", true);
test("foo/+/ba℞/#", "foo/baz/ba℞/#", true);
test("foo/+/ba℞/#", "foo/baz/+/#", false);
test("/+//#", "/foo///#", true);
test("#", "#", true);
test("#", "+", true);
test("/#", "+", false);
test("/#", "/+", true);
test("/+", "#", false);
test("/+", "+", false);
test("+/+", "topic/topic", true);
test("+/+", "topic/topic/", false);
test("+", "#", false);
test("+", "+", true);
test("a/b/c/d/e", "a/b/c/d/e", true);
test("a/b/ /d/e", "a/b/c/d/e", false);
return 0;
}
#endif
+1
View File
@@ -30,3 +30,4 @@ _mosquitto_property_free_all
_mosquitto_realloc
_mosquitto_set_username
_mosquitto_strdup
_mosquitto_topic_matches_sub
+1
View File
@@ -31,4 +31,5 @@
mosquitto_realloc;
mosquitto_set_username;
mosquitto_strdup;
mosquitto_topic_matches_sub;
};
+4
View File
@@ -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
* ============================================================ */
+2 -3
View File
@@ -816,9 +816,7 @@ static int pwfile__parse(const char *file, struct mosquitto__unpwd **root)
}
#ifdef WITH_TLS
static void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item)
void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item)
{
mosquitto__free(item->username);
mosquitto__free(item->password);
@@ -828,6 +826,7 @@ static void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__
}
#ifdef WITH_TLS
static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
{
struct mosquitto__unpwd *u, *tmp;
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
add_client_command = { "commands": [{
"command": "createClient", "username": "user_one",
"password": "password", "clientid": "cid",
"textName": "Name", "textDescription": "Description",
"roleName": "", "correlationData": "2" }]
}
add_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
add_client_repeat_response = {'responses':[{"command":"createClient","error":"Client already exists", "correlationData":"2"}]}
list_clients_command = { "commands": [{
"command": "listClients", "verbose": False, "correlationData": "10"}]
}
list_clients_response = {'responses': [{"command": "listClients", "data":{"totalCount":1, "clients":["user_one"]},"correlationData":"10"}]}
list_clients_verbose_command = { "commands": [{
"command": "listClients", "verbose": True, "correlationData": "20"}]
}
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{"totalCount":1, "clients":[
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
"groups":[], "roles":[]}]}, "correlationData":"20"}]}
get_client_command = { "commands": [{
"command": "getClient", "username": "user_one"}]}
get_client_response = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
'textName': 'Name', 'textDescription': 'Description', 'groups': [], 'roles': []}}}]}
set_client_password_command = {"commands": [{
"command": "setClientPassword", "username": "user_one", "password": "password"}]}
set_client_password_response = {"responses": [{"command":"setClientPassword"}]}
delete_client_command = { "commands": [{
"command": "deleteClient", "username": "user_one"}]}
delete_client_response = {'responses':[{'command': 'deleteClient'}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Add client
command_check(sock, add_client_command, add_client_response)
# List clients non-verbose
command_check(sock, list_clients_command, list_clients_response)
# List clients verbose
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Get client
command_check(sock, get_client_command, get_client_response)
# List clients non-verbose
command_check(sock, list_clients_command, list_clients_response)
# List clients verbose
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
# Add duplicate client
command_check(sock, add_client_command, add_client_repeat_response)
# Set client password
command_check(sock, set_client_password_command, set_client_password_response)
# Delete client
command_check(sock, delete_client_command, delete_client_response)
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response, msg=""):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(msg)
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
create_client_command = { "commands": [{
"command": "createClient", "username": "user_one",
"password": "password", "clientid": "cid",
"textName": "Name", "textDescription": "Description",
"roleName": "", "correlationData": "2" }]}
create_client_response = {'responses':[{"command":"createClient","correlationData":"2"}]}
create_group_command = { "commands": [{
"command": "createGroup", "groupName": "group_one",
"textName": "Name", "textDescription": "Description",
"correlationData":"3"}]}
create_group_response = {'responses':[{"command":"createGroup","correlationData":"3"}]}
create_group_repeat_response = {'responses':[{"command":"createGroup","error":"Group already exists","correlationData":"3"}]}
list_groups_command = { "commands": [{
"command": "listGroups", "verbose": False, "correlationData": "10"}]}
list_groups_response = {'responses':[{"command": "listGroups", "data":{"totalCount":1, "groups":["group_one"]},"correlationData":"10"}]}
list_groups_verbose_command = { "commands": [{
"command": "listGroups", "verbose": True, "correlationData": "15"}]}
list_groups_verbose_response = {'responses':[{'command': 'listGroups', 'data': {"totalCount":1, 'groups':
[{'groupName': 'group_one', 'textName': 'Name', 'textDescription': 'Description', 'clients': [
{"username":"user_one"}], "roles":[]}]},
'correlationData': '15'}]}
list_clients_verbose_command = { "commands": [{
"command": "listClients", "verbose": True, "correlationData": "20"}]}
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{"totalCount":1, "clients":[
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
"groups":[{"groupName":"group_one"}], "roles":[]}]}, "correlationData":"20"}]}
get_group_command = { "commands": [{"command": "getGroup", "groupName":"group_one"}]}
get_group_response = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
'textName':'Name', 'textDescription':'Description', 'clients': [{"username":"user_one"}], 'roles': []}}}]}
add_client_to_group_command = {"commands": [{"command":"addGroupClient", "username":"user_one",
"groupName": "group_one", "correlationData":"1234"}]}
add_client_to_group_response = {'responses':[{'command': 'addGroupClient', 'correlationData': '1234'}]}
remove_client_from_group_command = {"commands": [{"command":"removeGroupClient", "username":"user_one",
"groupName": "group_one", "correlationData":"4321"}]}
remove_client_from_group_response = {'responses':[{'command': 'removeGroupClient', 'correlationData': '4321'}]}
delete_group_command = {"commands": [{"command":"deleteGroup", "groupName":"group_one", "correlationData":"5678"}]}
delete_group_response = {'responses':[{"command":"deleteGroup", "correlationData":"5678"}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Add client
command_check(sock, create_client_command, create_client_response)
# Add group
command_check(sock, create_group_command, create_group_response)
# Add client to group
command_check(sock, add_client_to_group_command, add_client_to_group_response)
# Get group
command_check(sock, get_group_command, get_group_response)
# List groups non-verbose
command_check(sock, list_groups_command, list_groups_response)
# List groups verbose
command_check(sock, list_groups_verbose_command, list_groups_verbose_response, "list groups")
# List clients verbose
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Add duplicate group
command_check(sock, create_group_command, create_group_repeat_response)
# Remove client from group
command_check(sock, remove_client_from_group_command, remove_client_from_group_response)
# Add client back to group
command_check(sock, add_client_to_group_command, add_client_to_group_response)
# Delete group entirely
command_check(sock, delete_group_command, delete_group_response)
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response, msg=""):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(msg)
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
create_client_command = { "commands": [{
"command": "createClient", "username": "user_one",
"password": "password", "clientid": "cid",
"textName": "Name", "textDescription": "Description",
"correlationData": "2" }]
}
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
create_groups_command = { "commands": [
{
"command": "createGroup", "groupName": "group_one",
"textName": "Name", "textDescription": "Description",
"correlationData": "12"
},
{
"command": "createGroup", "groupName": "group_two",
"textName": "Name", "textDescription": "Description",
"correlationData": "13"
}
]
}
create_groups_response = {'responses': [
{'command': 'createGroup', 'correlationData': '12'},
{'command': 'createGroup', 'correlationData': '13'}
]}
create_roles_command = { "commands": [
{
"command": "createRole", "roleName": "role_one",
"textName": "Name", "textDescription": "Description",
"acls":[], "correlationData": "21"
},
{
"command": "createRole", "roleName": "role_two",
"textName": "Name", "textDescription": "Description",
"acls":[], "correlationData": "22"
},
{
"command": "createRole", "roleName": "role_three",
"textName": "Name", "textDescription": "Description",
"acls":[], "correlationData": "23"
}
]
}
create_roles_response = {'responses': [
{'command': 'createRole', 'correlationData': '21'},
{'command': 'createRole', 'correlationData': '22'},
{'command': 'createRole', 'correlationData': '23'}
]}
modify_client_command1 = { "commands": [{
"command": "modifyClient", "username": "user_one",
"textName": "Modified name", "textDescription": "Modified description",
"roles":[
{'roleName':'role_one', 'priority':2},
{'roleName':'role_two'},
{'roleName':'role_three', 'priority':10}
],
"groups":[
{'groupName':'group_one', 'priority':3},
{'groupName':'group_two', 'priority':8}
],
"correlationData": "3" }]
}
modify_client_response1 = {'responses': [{'command': 'modifyClient', 'correlationData': '3'}]}
modify_client_command2 = { "commands": [{
"command": "modifyClient", "username": "user_one",
"textName": "Modified name", "textDescription": "Modified description",
"groups":[],
"correlationData": "4" }]
}
modify_client_response2 = {'responses': [{'command': 'modifyClient', 'correlationData': '4'}]}
get_client_command1 = { "commands": [{
"command": "getClient", "username": "user_one"}]}
get_client_response1 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
'textName': 'Name', 'textDescription': 'Description',
'groups': [],
'roles': [],
}}}]}
get_client_command2 = { "commands": [{
"command": "getClient", "username": "user_one"}]}
get_client_response2 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
'textName': 'Modified name', 'textDescription': 'Modified description',
'groups': [
{'groupName':'group_two', 'priority':8},
{'groupName':'group_one', 'priority':3}
],
'roles': [
{'roleName':'role_three', 'priority':10},
{'roleName':'role_one', 'priority':2},
{'roleName':'role_two'}
]}}}]}
get_client_command3 = { "commands": [{
"command": "getClient", "username": "user_one"}]}
get_client_response3 = {'responses':[{'command': 'getClient', 'data': {'client': {'username': 'user_one', 'clientid': 'cid',
'textName': 'Modified name', 'textDescription': 'Modified description',
'groups': [],
'roles': [
{'roleName':'role_three', 'priority':10},
{'roleName':'role_one', 'priority':2},
{'roleName':'role_two'}
]}}}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Create client
command_check(sock, create_client_command, create_client_response)
# Create groups
command_check(sock, create_groups_command, create_groups_response)
# Create role
command_check(sock, create_roles_command, create_roles_response)
# Get client
command_check(sock, get_client_command1, get_client_response1, "get client 1")
# Modify client - with groups
command_check(sock, modify_client_command1, modify_client_response1)
# Get client
command_check(sock, get_client_command2, get_client_response2, "get client 2a")
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Get client
command_check(sock, get_client_command2, get_client_response2, "get client 2b")
# Modify client - without groups
command_check(sock, modify_client_command2, modify_client_response2)
# Get client
command_check(sock, get_client_command3, get_client_response3, "get client 3")
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
pass
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response, msg=""):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(msg)
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
create_client_command = { "commands": [{
"command": "createClient", "username": "user_one",
"password": "password", "clientid": "cid",
"textName": "Name", "textDescription": "Description",
"correlationData": "2" }]
}
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
create_group_command = { "commands": [{
"command": "createGroup", "groupName": "group_one",
"textName": "Name", "textDescription": "Description",
"roleName": "", "correlationData": "2" }]
}
create_group_response = {'responses': [{'command': 'createGroup', 'correlationData': '2'}]}
create_role_command = { "commands": [
{
"command": "createRole", "roleName": "role_one",
"textName": "Name", "textDescription": "Description",
"acls":[], "correlationData": "2"
},
{
"command": "createRole", "roleName": "role_two",
"textName": "Name", "textDescription": "Description",
"acls":[], "correlationData": "3"
}
]
}
create_role_response = {'responses': [
{'command': 'createRole', 'correlationData': '2'},
{'command': 'createRole', 'correlationData': '3'}
]}
modify_group_command1 = { "commands": [{
"command": "modifyGroup", "groupName": "group_one",
"textName": "Modified name", "textDescription": "Modified description",
"roles":[{'roleName':'role_one'}],
"clients":[{'username':'user_one'}],
"correlationData": "3" }]
}
modify_group_response1 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
modify_group_command2 = { "commands": [{
"command": "modifyGroup", "groupName": "group_one",
"textName": "Modified name", "textDescription": "Modified description",
"roles":[
{'roleName':'role_one', 'priority':99},
{'roleName':'role_two', 'priority':87}
],
"clients":[],
"correlationData": "3" }]
}
modify_group_response2 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
modify_group_command3 = { "commands": [{
"command": "modifyGroup", "groupName": "group_one",
"textName": "Modified name", "textDescription": "Modified description",
"roles":[],
"clients":[],
"correlationData": "3" }]
}
modify_group_response3 = {'responses': [{'command': 'modifyGroup', 'correlationData': '3'}]}
get_group_command1 = { "commands": [{
"command": "getGroup", "groupName": "group_one"}]}
get_group_response1 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
'textName': 'Name', 'textDescription': 'Description',
'clients':[],
'roles': []}}}]}
get_group_command2 = { "commands": [{
"command": "getGroup", "groupName": "group_one"}]}
get_group_response2 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
'textName': 'Modified name', 'textDescription': 'Modified description',
'clients':[{'username':'user_one'}],
'roles': [{'roleName':'role_one'}]}}}]}
get_group_command3 = { "commands": [{
"command": "getGroup", "groupName": "group_one"}]}
get_group_response3 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
'textName': 'Modified name', 'textDescription': 'Modified description',
'clients':[],
'roles': [
{'roleName':'role_one', 'priority':99},
{'roleName':'role_two', 'priority':87}
]}}}]}
get_group_command4 = { "commands": [{
"command": "getGroup", "groupName": "group_one"}]}
get_group_response4 = {'responses':[{'command': 'getGroup', 'data': {'group': {'groupName': 'group_one',
'textName': 'Modified name', 'textDescription': 'Modified description',
'clients':[],
'roles': []}}}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Create client
command_check(sock, create_client_command, create_client_response)
# Create group
command_check(sock, create_group_command, create_group_response)
# Create role
command_check(sock, create_role_command, create_role_response)
# Get group
command_check(sock, get_group_command1, get_group_response1, "get group 1")
# Modify group
command_check(sock, modify_group_command1, modify_group_response1)
# Get group
command_check(sock, get_group_command2, get_group_response2, "get group 2a")
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Get group
command_check(sock, get_group_command2, get_group_response2, "get group 2b")
# Modify group
command_check(sock, modify_group_command2, modify_group_response2)
# Get group
command_check(sock, get_group_command3, get_group_response3, "get group 3")
# Modify group
command_check(sock, modify_group_command3, modify_group_response3)
# Get group
command_check(sock, get_group_command4, get_group_response4, "get group 4")
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
pass
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
create_role_command = { "commands": [{
"command": "createRole", "roleName": "role_one",
"textName": "Name", "textDescription": "Description",
"acls":[
{
"aclType": "publishClientToBroker",
"allow": True,
"topic": "topic/#",
"priority": 8
},
{
"aclType": "publishClientToBroker",
"allow": True,
"topic": "topic/2/#",
"priority": 9
}
], "correlationData": "2" }]
}
create_role_response = {'responses': [{'command': 'createRole', 'correlationData': '2'}]}
modify_role_command = { "commands": [{
"command": "modifyRole", "roleName": "role_one",
"textName": "Modified name", "textDescription": "Modified description",
"correlationData": "3" }]
}
modify_role_response = {'responses': [{'command': 'modifyRole', 'correlationData': '3'}]}
get_role_command1 = { "commands": [{"command": "getRole", "roleName": "role_one"}]}
get_role_response1 = {'responses':[{'command': 'getRole', 'data': {'role': {'roleName': 'role_one',
'textName': 'Name', 'textDescription': 'Description',
'acls': [
{
"aclType": "publishClientToBroker",
"topic": "topic/2/#",
"allow": True,
"priority": 9
},
{
"aclType": "publishClientToBroker",
"topic": "topic/#",
"allow": True,
"priority": 8
}
]}}}]}
get_role_command2 = { "commands": [{
"command": "getRole", "roleName": "role_one"}]}
get_role_response2 = {'responses':[{'command': 'getRole', 'data': {'role': {'roleName': 'role_one',
'textName': 'Modified name', 'textDescription': 'Modified description',
'acls': [
{
"aclType": "publishClientToBroker",
"topic": "topic/2/#",
"allow": True,
"priority": 9
},
{
"aclType": "publishClientToBroker",
"topic": "topic/#",
"allow": True,
"priority": 8
}
]}}}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Add role
command_check(sock, create_role_command, create_role_response)
# Get role
command_check(sock, get_role_command1, get_role_response1)
# Modify role
command_check(sock, modify_role_command, modify_role_response)
# Get role
command_check(sock, get_role_command2, get_role_response2)
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Get role
command_check(sock, get_role_command2, get_role_response2)
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
+220
View File
@@ -0,0 +1,220 @@
#!/usr/bin/env python3
from mosq_test_helper import *
import json
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")
f.write("plugin ../../plugins/dynamic-security/mosquitto_dynamic_security.so\n")
f.write("plugin_opt_config_file %d/dynamic-security.json\n" % (port))
def command_check(sock, command_payload, expected_response, msg=""):
command_packet = mosq_test.gen_publish(topic="$CONTROL/dynamic-security/v1", qos=0, payload=json.dumps(command_payload))
sock.send(command_packet)
response = json.loads(mosq_test.read_publish(sock))
if response != expected_response:
print(msg)
print(expected_response)
print(response)
raise ValueError(response)
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
create_client_command = { "commands": [{
"command": "createClient", "username": "user_one",
"password": "password", "clientid": "cid",
"textName": "Name", "textDescription": "Description",
"roleName": "", "correlationData": "2" }]
}
create_client_response = {'responses': [{'command': 'createClient', 'correlationData': '2'}]}
create_group_command = { "commands": [{
"command": "createGroup", "groupName": "group_one",
"textName": "Name", "textDescription": "Description",
"correlationData":"3"}]}
create_group_response = {'responses':[{"command":"createGroup","correlationData":"3"}]}
create_role_command = { "commands": [{'command': 'createRole', 'correlationData': '3',
"roleName": "basic", "acls":[
{"aclType":"publishClientToBroker", "topic": "out/#", "priority":3, "allow": True}], "textName":"name", "textDescription":"desc"
}]}
create_role_response = {'responses': [{'command': 'createRole', 'correlationData': '3'}]}
add_role_to_client_command = {"commands": [{'command': 'addClientRole', "username": "user_one",
"roleName": "basic"}]}
add_role_to_client_response = {'responses': [{'command': 'addClientRole'}]}
add_role_to_group_command = {"commands": [{'command': 'addGroupRole', "groupName": "group_one",
"roleName": "basic"}]}
add_role_to_group_response = {'responses': [{'command': 'addGroupRole'}]}
list_roles_verbose_command1 = { "commands": [{
"command": "listRoles", "verbose": True, "correlationData": "21"}]
}
list_roles_verbose_response1 = {'responses': [{'command': 'listRoles', 'data':
{'totalCount':1, 'roles': [{'roleName': 'basic', "textName": "name", "textDescription": "desc",
'acls': [{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True}]
}]}, 'correlationData': '21'}]}
add_acl_command = {"commands": [{'command': "addRoleACL", "roleName":"basic", "aclType":"subscribeLiteral",
"topic":"basic/out", "priority":1, "allow":True}]}
add_acl_response = {'responses': [{'command': 'addRoleACL'}]}
list_roles_verbose_command2 = { "commands": [{
"command": "listRoles", "verbose": True, "correlationData": "22"}]
}
list_roles_verbose_response2 = {'responses': [{'command': 'listRoles', 'data': {'totalCount':1, 'roles':
[{'roleName': 'basic', 'textName': 'name', 'textDescription': 'desc', 'acls':
[{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True},
{'aclType':'subscribeLiteral', 'topic': 'basic/out', 'priority': 1, 'allow': True}],
}]}, 'correlationData': '22'}]}
get_role_command = {"commands": [{'command': "getRole", "roleName":"basic"}]}
get_role_response = {'responses': [{'command': 'getRole', 'data': {'role':
{'roleName': 'basic', 'textName': 'name', 'textDescription': 'desc', 'acls':
[{'aclType':'publishClientToBroker', 'topic': 'out/#', 'priority': 3, 'allow': True},
{'aclType':'subscribeLiteral', 'topic': 'basic/out', 'priority': 1, 'allow': True}],
}}}]}
remove_acl_command = {"commands": [{'command': "removeRoleACL", "roleName":"basic", "aclType":"subscribeLiteral",
"topic":"basic/out"}]}
remove_acl_response = {'responses': [{'command': 'removeRoleACL'}]}
delete_role_command = {"commands": [{'command': "deleteRole", "roleName":"basic"}]}
delete_role_response = {"responses": [{"command": "deleteRole"}]}
list_clients_verbose_command = { "commands": [{
"command": "listClients", "verbose": True, "correlationData": "20"}]
}
list_clients_verbose_response = {'responses':[{"command": "listClients", "data":{'totalCount':1, "clients":[
{"username":"user_one", "clientid":"cid", "textName":"Name", "textDescription":"Description",
"groups":[], "roles":[{'roleName':'basic'}]}]}, "correlationData":"20"}]}
list_groups_verbose_command = { "commands": [{
"command": "listGroups", "verbose": True, "correlationData": "20"}]
}
list_groups_verbose_response = {'responses':[{"command": "listGroups", "data":{'totalCount':1, "groups":[
{"groupName":"group_one", "textName":"Name", "textDescription":"Description",
"clients":[], "roles":[{'roleName':'basic'}]}]}, "correlationData":"20"}]}
remove_role_from_client_command = {"commands": [{'command': 'removeClientRole', "username": "user_one",
"roleName": "basic"}]}
remove_role_from_client_response = {'responses': [{'command': 'removeClientRole'}]}
remove_role_from_group_command = {"commands": [{'command': 'removeGroupRole', "groupName": "group_one",
"roleName": "basic"}]}
remove_role_from_group_response = {'responses': [{'command': 'removeGroupRole'}]}
rc = 1
keepalive = 10
connect_packet = mosq_test.gen_connect("ctrl-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 2
subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/#", 1)
suback_packet = mosq_test.gen_suback(mid, 1)
try:
os.mkdir(str(port))
with open("%d/dynamic-security.json" % port, 'w') as f:
f.write('{"defaultACLAction": {"publishClientToBroker":"allow", "publishBrokerToClient":"allow", "subscribe":"allow", "unsubscribe":"allow"}}')
except FileExistsError:
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
try:
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# Create client
command_check(sock, create_client_command, create_client_response)
# Create group
command_check(sock, create_group_command, create_group_response)
# Create role
command_check(sock, create_role_command, create_role_response)
# Add role to client
command_check(sock, add_role_to_client_command, add_role_to_client_response)
# Add role to group
command_check(sock, add_role_to_group_command, add_role_to_group_response)
# List clients verbose
command_check(sock, list_clients_verbose_command, list_clients_verbose_response)
# List groups verbose
command_check(sock, list_groups_verbose_command, list_groups_verbose_response)
# List roles verbose 1
command_check(sock, list_roles_verbose_command1, list_roles_verbose_response1, "list roles verbose 1a")
# Add ACL
command_check(sock, add_acl_command, add_acl_response)
# List roles verbose 2
command_check(sock, list_roles_verbose_command2, list_roles_verbose_response2, "list roles verbose 2a")
# Kill broker and restart, checking whether our changes were saved.
broker.terminate()
broker.wait()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
# List roles verbose 2
command_check(sock, list_roles_verbose_command2, list_roles_verbose_response2, "list roles verbose 2b")
# Get role
command_check(sock, get_role_command, get_role_response)
# Remove ACL
command_check(sock, remove_acl_command, remove_acl_response)
# List roles verbose 1
command_check(sock, list_roles_verbose_command1, list_roles_verbose_response1, "list roles verbose 1b")
# Remove role from client
command_check(sock, remove_role_from_client_command, remove_role_from_client_response)
# Remove role from group
command_check(sock, remove_role_from_group_command, remove_role_from_group_response)
# Delete role
command_check(sock, delete_role_command, delete_role_response)
rc = 0
sock.close()
except mosq_test.TestError:
pass
finally:
os.remove(conf_file)
try:
os.remove(f"{port}/dynamic-security.json")
except FileNotFoundError:
pass
os.rmdir(f"{port}")
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)
-55
View File
@@ -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