diff --git a/ChangeLog.txt b/ChangeLog.txt index 329145ca..af4ab82f 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -47,6 +47,8 @@ Broker: match the MQTT protocol version numbers, not internal Mosquitto values. - Add the `websockets_origin` option to allow optional enforcement of origin when a connection attempts an upgrade to WebSockets. +- Allow mosquitto_ctrl dynsec module to update passwords in files rather than + having to connect to a broker. Client library: - Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair diff --git a/apps/mosquitto_ctrl/CMakeLists.txt b/apps/mosquitto_ctrl/CMakeLists.txt index 97dbcf15..35c7837e 100644 --- a/apps/mosquitto_ctrl/CMakeLists.txt +++ b/apps/mosquitto_ctrl/CMakeLists.txt @@ -7,6 +7,7 @@ if(WITH_TLS AND CJSON_FOUND) mosquitto_ctrl.c mosquitto_ctrl.h client.c dynsec.c + ../../plugins/dynamic-security/hash.c dynsec_client.c dynsec_group.c dynsec_role.c @@ -26,6 +27,7 @@ if(WITH_TLS AND CJSON_FOUND) "${mosquitto_SOURCE_DIR}/apps/mosquitto_passwd" "${mosquitto_SOURCE_DIR}/include" "${mosquitto_SOURCE_DIR}/lib" + "${mosquitto_SOURCE_DIR}/plugins/dynamic-security" "${mosquitto_SOURCE_DIR}/src" ) diff --git a/apps/mosquitto_ctrl/Makefile b/apps/mosquitto_ctrl/Makefile index 502f0dac..2afd4dbb 100644 --- a/apps/mosquitto_ctrl/Makefile +++ b/apps/mosquitto_ctrl/Makefile @@ -12,13 +12,14 @@ LIBMOSQ:=../../lib/libmosquitto.a endif endif -LOCAL_CPPFLAGS:=-I../mosquitto_passwd -DWITH_CJSON +LOCAL_CPPFLAGS:=-I../mosquitto_passwd -I../../plugins/dynamic-security -DWITH_CJSON OBJS= mosquitto_ctrl.o \ client.o \ dynsec.o \ dynsec_client.o \ dynsec_group.o \ + dynsec_hash.o \ dynsec_role.o \ get_password.o \ memory_mosq.o \ @@ -56,6 +57,9 @@ client.o : client.c mosquitto_ctrl.h dynsec.o : dynsec.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(APP_CPPFLAGS) $(APP_CFLAGS) -c $< -o $@ +dynsec_hash.o : ../../plugins/dynamic-security/hash.c ../../plugins/dynamic-security/dynamic_security.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 $@ diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index f3ad6fe3..d673e863 100644 --- a/apps/mosquitto_ctrl/dynsec.c +++ b/apps/mosquitto_ctrl/dynsec.c @@ -45,6 +45,8 @@ void dynsec__print_usage(void) printf("Create a new client: createClient [-c clientid] [-p password]\n"); printf("Delete a client: deleteClient \n"); printf("Set a client password: setClientPassword [password]\n"); + printf("Set a client password on an existing file:\n"); + printf(" mosquitto_ctrl -f dynsec setClientPassword \n"); printf("Set a client id: setClientId [clientid]\n"); printf("Add a role to a client: addClientRole [priority]\n"); printf(" Higher priority (larger numerical value) roles are evaluated first.\n"); @@ -785,6 +787,8 @@ int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl) return -1; }else if(!strcasecmp(argv[0], "init")){ return dynsec_init(argc-1, &argv[1]); + }else if(ctrl->cfg.data_file && !strcasecmp(argv[0], "setClientPassword")){ + return dynsec_client__file_set_password(argc-1, &argv[1], ctrl->cfg.data_file); } /* The remaining commands need a network connection and JSON command. */ diff --git a/apps/mosquitto_ctrl/dynsec_client.c b/apps/mosquitto_ctrl/dynsec_client.c index ed900eff..a847bb5d 100644 --- a/apps/mosquitto_ctrl/dynsec_client.c +++ b/apps/mosquitto_ctrl/dynsec_client.c @@ -24,6 +24,7 @@ Contributors: #include "mosquitto_ctrl.h" #include "get_password.h" #include "password_mosq.h" +#include "dynamic_security.h" int dynsec_client__create(int argc, char *argv[], cJSON *j_command) { @@ -149,6 +150,165 @@ int dynsec_client__set_id(int argc, char *argv[], cJSON *j_command) } } +int dynsec_client__file_set_password(int argc, char *argv[], const char *file) +{ + char *username = NULL, *password = NULL; + long len; + FILE *fptr; + char *fstr; + cJSON *j_tree, *j_clients, *j_client; + cJSON *j_username, *j_password, *j_salt, *j_iterations; + struct dynsec__client client; + char *pw_buf = NULL, *salt_buf = NULL; + char *json_str; + int i; + + memset(&client, 0, sizeof(client)); + + if(argc >= 2){ + username = argv[0]; + password = argv[1]; + }else{ + return MOSQ_ERR_INVAL; + } + for(i=2; ivaluestring, username)){ + if(dynsec_auth__pw_hash(&client, password, client.pw.password_hash, sizeof(client.pw.password_hash), true) != MOSQ_ERR_SUCCESS){ + fprintf(stderr, "Error: Problem generating password hash.\n"); + cJSON_Delete(j_tree); + return MOSQ_ERR_UNKNOWN; + } + + if(base64__encode(client.pw.password_hash, sizeof(client.pw.password_hash), &pw_buf) != MOSQ_ERR_SUCCESS){ + fprintf(stderr, "Error: Problem generating password hash.\n"); + cJSON_Delete(j_tree); + free(pw_buf); + free(salt_buf); + return MOSQ_ERR_UNKNOWN; + } + if(base64__encode(client.pw.salt, client.pw.salt_len, &salt_buf) != MOSQ_ERR_SUCCESS){ + fprintf(stderr, "Error: Problem generating password hash.\n"); + cJSON_Delete(j_tree); + free(pw_buf); + free(salt_buf); + return MOSQ_ERR_UNKNOWN; + } + j_password = cJSON_CreateString(pw_buf); + if(j_password == NULL){ + fprintf(stderr, "Error: Out of memory.\n"); + cJSON_Delete(j_tree); + free(pw_buf); + free(salt_buf); + return MOSQ_ERR_NOMEM; + } + j_salt = cJSON_CreateString(salt_buf); + if(j_salt == NULL){ + fprintf(stderr, "Error: Out of memory.\n"); + cJSON_Delete(j_password); + cJSON_Delete(j_tree); + free(pw_buf); + free(salt_buf); + return MOSQ_ERR_NOMEM; + } + j_iterations = cJSON_CreateNumber(client.pw.iterations); + if(j_iterations == NULL){ + fprintf(stderr, "Error: Out of memory.\n"); + cJSON_Delete(j_password); + cJSON_Delete(j_salt); + cJSON_Delete(j_tree); + free(pw_buf); + free(salt_buf); + return MOSQ_ERR_NOMEM; + } + cJSON_ReplaceItemInObject(j_client, "password", j_password); + cJSON_ReplaceItemInObject(j_client, "salt", j_salt); + cJSON_ReplaceItemInObject(j_client, "iterations", j_iterations); + free(pw_buf); + free(salt_buf); + + json_str = cJSON_Print(j_tree); + cJSON_Delete(j_tree); + if(json_str == NULL){ + fprintf(stderr, "Error: Out of memory.\n"); + return MOSQ_ERR_NOMEM; + } + fptr = fopen(file, "wb"); + if(fptr == NULL){ + fprintf(stderr, "Error: Unable to write to %s.\n", file); + free(json_str); + return MOSQ_ERR_UNKNOWN; + } + fprintf(fptr, "%s", json_str); + free(json_str); + fclose(fptr); + return MOSQ_ERR_SUCCESS; + } + } + } + } + + fprintf(stderr, "Error: Client %s not found.\n", username); + return MOSQ_ERR_SUCCESS; +} + int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command) { char *username = NULL, *password = NULL; diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.c b/apps/mosquitto_ctrl/mosquitto_ctrl.c index aa706a61..cded2100 100644 --- a/apps/mosquitto_ctrl/mosquitto_ctrl.c +++ b/apps/mosquitto_ctrl/mosquitto_ctrl.c @@ -97,7 +97,9 @@ int main(int argc, char *argv[]) /* Usage print */ rc = 0; }else if(rc == MOSQ_ERR_SUCCESS){ - rc = client_request_response(&ctrl); + if(ctrl.cfg.data_file == NULL){ + rc = client_request_response(&ctrl); + } }else if(rc == MOSQ_ERR_UNKNOWN){ /* Message printed already */ }else{ diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.h b/apps/mosquitto_ctrl/mosquitto_ctrl.h index fce2a78d..0dd86cb9 100644 --- a/apps/mosquitto_ctrl/mosquitto_ctrl.h +++ b/apps/mosquitto_ctrl/mosquitto_ctrl.h @@ -64,6 +64,7 @@ struct mosq_config { char *socks5_username; char *socks5_password; #endif + char *data_file; }; struct mosq_ctrl { @@ -95,6 +96,7 @@ int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, con 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__enable_disable(int argc, char *argv[], cJSON *j_command, const char *command); +int dynsec_client__file_set_password(int argc, char *argv[], const char *file); 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_id(int argc, char *argv[], cJSON *j_command); diff --git a/apps/mosquitto_ctrl/options.c b/apps/mosquitto_ctrl/options.c index 5c36e5db..b93863d8 100644 --- a/apps/mosquitto_ctrl/options.c +++ b/apps/mosquitto_ctrl/options.c @@ -81,6 +81,7 @@ void client_config_cleanup(struct mosq_config *cfg) free(cfg->socks5_username); free(cfg->socks5_password); #endif + free(cfg->data_file); } int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[]) @@ -187,6 +188,15 @@ static int client_config_line_proc(struct mosq_config *cfg, int *argc, char **ar #endif }else if(!strcmp(argv[0], "-d") || !strcmp(argv[0], "--debug")){ cfg->debug = true; + }else if(!strcmp(argv[0], "-f")){ + if((*argc) == 1){ + fprintf(stderr, "Error: -f argument given but no data file specified.\n\n"); + return 1; + }else{ + cfg->data_file = strdup(argv[1]); + } + argv++; + (*argc)--; }else if(!strcmp(argv[0], "--help")){ return 2; }else if(!strcmp(argv[0], "-h") || !strcmp(argv[0], "--host")){ diff --git a/plugins/dynamic-security/CMakeLists.txt b/plugins/dynamic-security/CMakeLists.txt index c90870a6..09bdfefa 100644 --- a/plugins/dynamic-security/CMakeLists.txt +++ b/plugins/dynamic-security/CMakeLists.txt @@ -25,6 +25,7 @@ if(CJSON_FOUND AND WITH_TLS) dynamic_security.h groups.c grouplist.c + hash.c json_help.c json_help.h ../../lib/misc_mosq.c ../../lib/misc_mosq.h diff --git a/plugins/dynamic-security/Makefile b/plugins/dynamic-security/Makefile index 46edee7f..c4968ea7 100644 --- a/plugins/dynamic-security/Makefile +++ b/plugins/dynamic-security/Makefile @@ -13,6 +13,7 @@ OBJS= \ config_init.o \ groups.o \ grouplist.o \ + hash.o \ json_help.o \ misc_mosq.o \ password_mosq.o \ @@ -57,6 +58,9 @@ groups.o : groups.c dynamic_security.h grouplist.o : grouplist.c dynamic_security.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) -c $< -o $@ +hash.o : hash.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 $@ diff --git a/plugins/dynamic-security/auth.c b/plugins/dynamic-security/auth.c index f559ed84..4376ba46 100644 --- a/plugins/dynamic-security/auth.c +++ b/plugins/dynamic-security/auth.c @@ -27,41 +27,6 @@ Contributors: #include "mosquitto.h" #include "mosquitto_broker.h" -/* ################################################################ - * # - * # 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){ - client->pw.salt_len = HASH_LEN; - if(RAND_bytes(client->pw.salt, (int)client->pw.salt_len) != 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, (int)strlen(password), - client->pw.salt, (int)client->pw.salt_len, iterations, - digest, password_hash_len, password_hash); -} - /* ################################################################ * # diff --git a/plugins/dynamic-security/hash.c b/plugins/dynamic-security/hash.c new file mode 100644 index 00000000..d9514982 --- /dev/null +++ b/plugins/dynamic-security/hash.c @@ -0,0 +1,67 @@ +/* +Copyright (c) 2020 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License 2.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + https://www.eclipse.org/legal/epl-2.0/ +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include "dynamic_security.h" +#include "mosquitto.h" +#include "mosquitto_broker.h" + +/* ################################################################ + * # + * # 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){ + client->pw.salt_len = HASH_LEN; + if(RAND_bytes(client->pw.salt, (int)client->pw.salt_len) != 1){ + return MOSQ_ERR_UNKNOWN; + } + if(client->pw.iterations > 0){ + iterations = client->pw.iterations; + }else{ + 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, (int)strlen(password), + client->pw.salt, (int)client->pw.salt_len, iterations, + digest, password_hash_len, password_hash); +}