From b49631df232a81e08169f835bd04a113a379476b Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Wed, 30 Nov 2022 13:46:35 +0000 Subject: [PATCH] Add mosquitto_passwd tests --- apps/mosquitto_passwd/mosquitto_passwd.c | 46 +++++----- test/apps/03-passwd-args.py | 41 +++++++++ test/apps/03-passwd-changes.py | 103 +++++++++++++++++++++++ test/apps/Makefile | 6 +- 4 files changed, 168 insertions(+), 28 deletions(-) create mode 100755 test/apps/03-passwd-args.py create mode 100755 test/apps/03-passwd-changes.py diff --git a/apps/mosquitto_passwd/mosquitto_passwd.c b/apps/mosquitto_passwd/mosquitto_passwd.c index 7edeae03..63b69bd1 100644 --- a/apps/mosquitto_passwd/mosquitto_passwd.c +++ b/apps/mosquitto_passwd/mosquitto_passwd.c @@ -109,10 +109,7 @@ static FILE *mpw_tmpfile(void) int log__printf(void *mosq, unsigned int level, const char *fmt, ...) { /* Stub for misc_mosq.c */ - UNUSED(mosq); - UNUSED(level); - UNUSED(fmt); - return 0; + UNUSED(mosq); UNUSED(level); UNUSED(fmt); return 0; } @@ -145,35 +142,30 @@ static int output_new_password(FILE *fptr, const char *username, const char *pas pw.hashtype = hashtype; - if(pw__hash(password, &pw, true, iterations)){ - fprintf(stderr, "Error: Unable to hash password.\n"); - return 1; - } - - rc = base64__encode(pw.salt, pw.salt_len, &salt64); + rc = pw__hash(password, &pw, true, iterations); if(rc){ - free(salt64); - fprintf(stderr, "Error: Unable to encode salt.\n"); - return 1; - } - - rc = base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64); - if(rc){ - free(salt64); - free(hash64); - fprintf(stderr, "Error: Unable to encode hash.\n"); - return 1; - } - - if(pw.hashtype == pw_sha512_pbkdf2){ - fprintf(fptr, "%s:$%d$%d$%s$%s\n", username, hashtype, iterations, salt64, hash64); + fprintf(stderr, "Error: Unable to hash password.\n"); }else{ - fprintf(fptr, "%s:$%d$%s$%s\n", username, hashtype, salt64, hash64); + rc = base64__encode(pw.salt, pw.salt_len, &salt64); + if(rc){ + fprintf(stderr, "Error: Unable to encode salt.\n"); + }else{ + rc = base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64); + if(rc){ + fprintf(stderr, "Error: Unable to encode hash.\n"); + }else{ + if(pw.hashtype == pw_sha512_pbkdf2){ + fprintf(fptr, "%s:$%d$%d$%s$%s\n", username, hashtype, iterations, salt64, hash64); + }else{ + fprintf(fptr, "%s:$%d$%s$%s\n", username, hashtype, salt64, hash64); + } + } + } } free(salt64); free(hash64); - return 0; + return rc; } diff --git a/test/apps/03-passwd-args.py b/test/apps/03-passwd-args.py new file mode 100755 index 00000000..ce4552cd --- /dev/null +++ b/test/apps/03-passwd-args.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Test parsing of command line args and errors. Does not test arg functionality. + +from mosq_test_helper import * + +def do_test(args, rc_expected, response=None, input=None): + proc = subprocess.run([mosq_test.get_build_root()+"/apps/mosquitto_passwd/mosquitto_passwd"] + + args, + capture_output=True, encoding='utf-8', timeout=2, input=input) + + if response is not None: + if proc.stderr != response: + print(len(proc.stderr)) + print(len(response)) + raise ValueError(proc.stderr) + + if proc.returncode != rc_expected: + print(proc.returncode) + raise ValueError(args) + +do_test([], 1) # For the usage message +do_test(["-H"], 1, response="Error: -H argument given but not enough other arguments.\n") +do_test(["-H", "nohash"], 1, response="Error: Unknown hash type 'nohash'\n") +do_test(["-I"], 1, response="Error: -I argument given but not enough other arguments.\n") +do_test(["-I", "0"], 1, response="Error: Number of iterations must be > 0.\n") +do_test(["-c", "-D"], 1, response="Error: -c and -D cannot be used together.\n") +do_test(["-c", "-U"], 1, response="Error: -c and -U cannot be used together.\n") +do_test(["-U", "-D"], 1, response="Error: -D and -U cannot be used together.\n") +do_test(["-b", "-D"], 1, response="Error: -b and -D cannot be used together.\n") +do_test(["-c", "-b"], 1, response="Error: -c argument given but password file, username, or password missing.\n") +do_test(["-c"], 1, response="Error: -c argument given but password file or username missing.\n") +do_test(["-D"], 1, response="Error: -D argument given but password file or username missing.\n") +do_test(["-U"], 1, response="Error: -U argument given but password file missing.\n") +do_test(["-D", "pwfile", "bad-username:"], 1, response="Error: Username must not contain the ':' character.\n") +do_test(["-D", "pwfile", "bad-username\n"], 1, response="Error: Username must not contain control characters.\n") +do_test(["-D", "pwfile", "a"*65536], 1, response="Error: Username must be less than 65536 characters long.\n") + +do_test(["-c", "file", "username"], 2, response="Error: Passwords do not match.\n", input="not\nmatching\n") + +exit(0) diff --git a/test/apps/03-passwd-changes.py b/test/apps/03-passwd-changes.py new file mode 100755 index 00000000..1a4f733f --- /dev/null +++ b/test/apps/03-passwd-changes.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +from mosq_test_helper import * +import json +import shutil +import signal + +def write_config(filename, pw_file, port): + with open(filename, 'w') as f: + f.write(f"listener {port}\n") + f.write("allow_anonymous false\n") + f.write(f"password_file {pw_file}\n") + +def client_check(port, username, password, rc): + connect_packet = mosq_test.gen_connect("pwd-test", username=username, password=password) + connack_packet = mosq_test.gen_connack(rc=rc) + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + sock.close() + + +def passwd_cmd(args, port, response=None, input=None): + proc = subprocess.run([mosq_test.get_build_root()+"/apps/mosquitto_passwd/mosquitto_passwd"] + + args, + capture_output=True, encoding='utf-8', timeout=2, input=input) + + if response is not None: + if proc.stdout != response: + print(len(proc.stdout)) + print(len(response)) + raise ValueError(proc.stdout) + + if proc.returncode != 0: + raise ValueError(args) + + +port = mosq_test.get_port() +conf_file = os.path.basename(__file__).replace('.py', '.conf') +pw_file = os.path.basename(__file__).replace('.py', '.pwfile') +write_config(conf_file, pw_file, port) + +# Generate initial password file +passwd_cmd(["-H", "sha512", "-c", "-b", pw_file, "user1", "pass1"], port) +passwd_cmd(["-H", "sha512-pbkdf2", pw_file, "user2"], port, input="cmd\ncmd\n") + +# Then start broker +broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port, nolog=True) + +try: + rc = 1 + client_check(port, "user1", "badpass", 5) + client_check(port, "user1", "pass1", 0) + client_check(port, "user2", "badpass", 5) + client_check(port, "user2", "cmd", 0) + client_check(port, "user3", "badpass", 5) + client_check(port, "user3", "goodpass", 5) + + # Update password + passwd_cmd(["-H", "sha512-pbkdf2", "-b", pw_file, "user1", "newpass"], port) + broker.send_signal(signal.SIGHUP) + + client_check(port, "user1", "badpass", 5) + client_check(port, "user1", "newpass", 0) + client_check(port, "user2", "badpass", 5) + client_check(port, "user2", "cmd", 0) + client_check(port, "user3", "badpass", 5) + client_check(port, "user3", "goodpass", 5) + + # New user + passwd_cmd(["-b", pw_file, "user3", "goodpass"], port) + broker.send_signal(signal.SIGHUP) + + client_check(port, "user1", "badpass", 5) + client_check(port, "user1", "newpass", 0) + client_check(port, "user2", "badpass", 5) + client_check(port, "user2", "cmd", 0) + client_check(port, "user3", "badpass", 5) + client_check(port, "user3", "goodpass", 0) + + # Delete user + passwd_cmd(["-D", pw_file, "user2"], port) + broker.send_signal(signal.SIGHUP) + + client_check(port, "user1", "badpass", 5) + client_check(port, "user1", "newpass", 0) + client_check(port, "user2", "badpass", 5) + client_check(port, "user2", "cmd", 5) + client_check(port, "user3", "badpass", 5) + client_check(port, "user3", "goodpass", 0) + + rc = 0 +except mosq_test.TestError: + pass +except Exception as err: + print(err) +finally: + os.remove(conf_file) + os.remove(pw_file) + broker.terminate() + if mosq_test.wait_for_subprocess(broker): + print("broker not terminated") + if rc == 0: rc=1 + +exit(rc) diff --git a/test/apps/Makefile b/test/apps/Makefile index 27f741eb..e2b19007 100644 --- a/test/apps/Makefile +++ b/test/apps/Makefile @@ -8,7 +8,7 @@ all : check : test ptest : test -test : 01 02 +test : 01 02 03 01 : ./01-db-dump-client-stats.py @@ -24,4 +24,8 @@ test : 01 02 ./02-ctrl-broker.py ./02-ctrl-dynsec.py +03 : + ./03-passwd-args.py + ./03-passwd-changes.py + clean: