Allow mosquitto_ctrl dynsec module to update passwords in files

pull/2386/head
Roger A. Light 4 years ago
parent 42b45e0c89
commit c862ffec8b

@ -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

@ -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"
)

@ -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 $@

@ -45,6 +45,8 @@ void dynsec__print_usage(void)
printf("Create a new client: createClient <username> [-c clientid] [-p password]\n");
printf("Delete a client: deleteClient <username>\n");
printf("Set a client password: setClientPassword <username> [password]\n");
printf("Set a client password on an existing file:\n");
printf(" mosquitto_ctrl -f <file> dynsec setClientPassword <username> <password>\n");
printf("Set a client id: setClientId <username> [clientid]\n");
printf("Add a role to a client: addClientRole <username> <rolename> [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. */

@ -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; i<argc; i++){
if(!strcmp(argv[i], "-i")){
if(i+1 == argc){
fprintf(stderr, "Error: -i argument given, but no iterations provided.\n");
return MOSQ_ERR_INVAL;
}
client.pw.iterations = atoi(argv[i+1]);
i++;
}else{
fprintf(stderr, "Error: Unknown argument: %s\n", argv[i]);
return MOSQ_ERR_INVAL;
}
}
fptr = fopen(file, "rb");
if(fptr == NULL){
fprintf(stderr, "Error: Unable to open %s.\n", file);
return MOSQ_ERR_INVAL;
}
fseek(fptr, 0, SEEK_END);
len = ftell(fptr);
fseek(fptr, 0, SEEK_SET);
if(len <= 0){
fprintf(stderr, "Error: %s is empty.\n", file);
fclose(fptr);
return MOSQ_ERR_INVAL;
}
fstr = calloc(1, (size_t)len+1);
if(fstr == NULL){
fclose(fptr);
return MOSQ_ERR_NOMEM;
}
if(fread(fstr, 1, (size_t)len, fptr) != (size_t)len){
fprintf(stderr, "Error: Incomplete read of %s.\n", file);
fclose(fptr);
return MOSQ_ERR_NOMEM;
}
fclose(fptr);
j_tree = cJSON_Parse(fstr);
free(fstr);
if(j_tree == NULL){
fprintf(stderr, "Error: %s is not valid JSON.\n", file);
return MOSQ_ERR_INVAL;
}
j_clients = cJSON_GetObjectItem(j_tree, "clients");
if(j_clients == NULL || !cJSON_IsArray(j_clients)){
fprintf(stderr, "Error: %s is not a valid dynamic-security config file.\n", file);
cJSON_Delete(j_tree);
return MOSQ_ERR_INVAL;
}
cJSON_ArrayForEach(j_client, j_clients){
if(cJSON_IsObject(j_client) == true){
j_username = cJSON_GetObjectItem(j_client, "username");
if(j_username && cJSON_IsString(j_username)){
if(!strcmp(j_username->valuestring, 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;

@ -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{

@ -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);

@ -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")){

@ -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

@ -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 $@

@ -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);
}
/* ################################################################
* #

@ -0,0 +1,67 @@
/*
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 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 <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"
/* ################################################################
* #
* # 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);
}
Loading…
Cancel
Save