diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..b9a96fe7 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,81 @@ +name: Windows build + +on: + workflow_dispatch: + push: + branches: [ "master", "fixes" ] + tags: [ "v[0-9]+.*" ] + pull_request: + branches: [ "master", "fixes" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + + cjson: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v3 + with: + repository: DaveGamble/cJSON + ref: v1.7.15 + + - name: Configure CMake cJSON + run: cmake -B ${{github.workspace}}/build64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_CJSON_TEST=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_SHARED_AND_STATIC_LIBS=OFF -DCJSON_BUILD_SHARED_LIBS=OFF -DCJSON_OVERRIDE_BUILD_SHARED_LIBS=OFF -DCMAKE_GENERATOR_PLATFORM=x64 + + - name: Build cJSON + run: cmake --build ${{github.workspace}}/build64 --config ${{env.BUILD_TYPE}} + + - name: Install cJSON + run: cmake --install ${{github.workspace}}/build64 --config ${{env.BUILD_TYPE}} + + - name: Upload cJSON + uses: actions/upload-artifact@v3 + with: + name: cjson-bin + path: C:\Program Files\cJSON + + mosquitto: + runs-on: windows-2022 + needs: + - cjson + env: + CJSON_DIR: C:\Program Files\cJSON + + steps: + - uses: actions/checkout@v3 + + - name: install openssl + run: choco install openssl + + - name: Download cJSON + uses: actions/download-artifact@v3 + with: + name: cjson-bin + path: C:\Program Files\cJSON + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DWITH_TESTS=OFF -DCMAKE_GENERATOR_PLATFORM=x64 -DCJSON_INCLUDE_DIR="C:/Program Files/cJSON/include" -DCJSON_LIBRARY="C:/Program Files/cJSON/lib/cjson.lib" + + - name: Build + run: cmake --build ${{github.workspace}}/build64 --config ${{env.BUILD_TYPE}} + + - uses: suisei-cn/actions-download-file@v1.0.1 + id: vcredist + name: Download VC redistributable + with: + url: https://aka.ms/vs/17/release/vc_redist.x64.exe + target: ${{github.workspace}}/installer/ + + - name: Installer + uses: joncloud/makensis-action@v3.7 + with: + script-file: ${{github.workspace}}/installer/mosquitto.nsi + + - name: Upload installer to artifacts + uses: actions/upload-artifact/@v2 + with: + name: installer + path: ${{ github.workspace }}/installer/mosquitto*.exe diff --git a/.gitignore b/.gitignore index b5bb1697..8866349a 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ test/unit/mosq_test test/unit/persist_read_test test/unit/persist_write_test test/unit/subs_test +test/unit/tls_test test/unit/out/ www/cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b8913c2d..c14a47bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.1) cmake_policy(SET CMP0042 NEW) project(mosquitto) -set (VERSION 2.0.15) +set (VERSION 2.0.16) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") diff --git a/ChangeLog.txt b/ChangeLog.txt index ffe19b80..0c797358 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,62 @@ +2.0.16 - 2023-08-16 +=================== + +Security: +- CVE-2023-28366: Fix memory leak in broker when clients send multiple QoS 2 + messages with the same message ID, but then never respond to the PUBREC + commands. +- CVE-2023-0809: Fix excessive memory being allocated based on malicious + initial packets that are not CONNECT packets. +- CVE-2023-3592: Fix memory leak when clients send v5 CONNECT packets with a + will message that contains invalid property types. +- Broker will now reject Will messages that attempt to publish to $CONTROL/. +- Broker now validates usernames provided in a TLS certificate or TLS-PSK + identity are valid UTF-8. +- Fix potential crash when loading invalid persistence file. +- Library will no longer allow single level wildcard certificates, e.g. *.com + +Broker: +- Fix $SYS messages being expired after 60 seconds and hence unchanged values + disappearing. +- Fix some retained topic memory not being cleared immediately after used. +- Fix error handling related to the `bind_interface` option. +- Fix std* files not being redirected when daemonising, when built with + assertions removed. Closes #2708. +- Fix default settings incorrectly allowing TLS v1.1. Closes #2722. +- Use line buffered mode for stdout. Closes #2354. Closes #2749. +- Fix bridges with non-matching cleansession/local_cleansession being expired + on start after restoring from persistence. Closes #2634. +- Fix connections being limited to 2048 on Windows. The limit is now 8192, + where supported. Closes #2732. +- Broker will log warnings if sensitive files are world readable/writable, or + if the owner/group is not the same as the user/group the broker is running + as. In future versions the broker will refuse to open these files. +- mosquitto_memcmp_const is now more constant time. +- Only register with DLT if DLT logging is enabled. +- Fix any possible case where a json string might be incorrectly loaded. This + could have caused a crash if a textname or textdescription field of a role was + not a string, when loading the dynsec config from file only. +- Dynsec plugin will not allow duplicate clients/groups/roles when loading + config from file, which matches the behaviour for when creating them. +- Fix heap overflow when reading corrupt config with "log_dest file". + +Client library: +- Use CLOCK_BOOTTIME when available, to keep track of time. This solves the + problem of the client OS sleeping and the client hence not being able to + calculate the actual time for keepalive purposes. Closes #2760. +- Fix default settings incorrectly allowing TLS v1.1. Closes #2722. +- Fix high CPU use on slow TLS connect. Closes #2794. + +Clients: +- Fix incorrect topic-alias property value in mosquitto_sub json output. +- Fix confusing message on TLS certificate verification. Closes #2746. + +Apps: +- mosquitto_passwd uses mkstemp() for backup files. +- `mosquitto_ctrl dynsec init` will refuse to overwrite an existing file, + without a race-condition. + + 2.0.15 - 2022-08-16 =================== diff --git a/README.md b/README.md index a0d275e8..c584394c 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,3 @@ Equivalent options for enabling/disabling features are available when using the ## Credits Mosquitto was written by Roger Light - -Master: [![Travis Build Status (master)](https://travis-ci.org/eclipse/mosquitto.svg?branch=master)](https://travis-ci.org/eclipse/mosquitto) -Develop: [![Travis Build Status (develop)](https://travis-ci.org/eclipse/mosquitto.svg?branch=develop)](https://travis-ci.org/eclipse/mosquitto) -Fixes: [![Travis Build Status (fixes)](https://travis-ci.org/eclipse/mosquitto.svg?branch=fixes)](https://travis-ci.org/eclipse/mosquitto) diff --git a/apps/db_dump/db_dump.c b/apps/db_dump/db_dump.c index 234e121c..7258d378 100644 --- a/apps/db_dump/db_dump.c +++ b/apps/db_dump/db_dump.c @@ -16,7 +16,6 @@ Contributors: Roger Light - initial implementation and documentation. */ -#include #include #include #include diff --git a/apps/mosquitto_ctrl/CMakeLists.txt b/apps/mosquitto_ctrl/CMakeLists.txt index d4466fe5..26dbe8d6 100644 --- a/apps/mosquitto_ctrl/CMakeLists.txt +++ b/apps/mosquitto_ctrl/CMakeLists.txt @@ -17,6 +17,7 @@ if (WITH_TLS AND CJSON_FOUND) dynsec_role.c ../mosquitto_passwd/get_password.c ../mosquitto_passwd/get_password.h ../../lib/memory_mosq.c ../../lib/memory_mosq.h + ../../lib/misc_mosq.c ../../lib/misc_mosq.h ../../src/memory_public.c options.c ../../src/password_mosq.c ../../src/password_mosq.h diff --git a/apps/mosquitto_ctrl/Makefile b/apps/mosquitto_ctrl/Makefile index 502f0dac..cf9ac0dc 100644 --- a/apps/mosquitto_ctrl/Makefile +++ b/apps/mosquitto_ctrl/Makefile @@ -23,6 +23,7 @@ OBJS= mosquitto_ctrl.o \ get_password.o \ memory_mosq.o \ memory_public.o \ + misc_mosq.o \ options.o \ password_mosq.o diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index c74147b6..929217af 100644 --- a/apps/mosquitto_ctrl/dynsec.c +++ b/apps/mosquitto_ctrl/dynsec.c @@ -23,6 +23,8 @@ Contributors: #include #ifndef WIN32 +# include +# include # include #endif @@ -30,6 +32,7 @@ Contributors: #include "mosquitto.h" #include "password_mosq.h" #include "get_password.h" +#include "misc_mosq.h" void dynsec__print_usage(void) { @@ -738,13 +741,6 @@ static int dynsec_init(int argc, char *argv[]) admin_password = password; } - fptr = fopen(filename, "rb"); - if(fptr){ - fclose(fptr); - fprintf(stderr, "dynsec init: '%s' already exists. Remove the file or use a different location..\n", filename); - return -1; - } - tree = init_create(admin_user, admin_password, "admin"); if(tree == NULL){ fprintf(stderr, "dynsec init: Out of memory.\n"); @@ -753,7 +749,17 @@ static int dynsec_init(int argc, char *argv[]) json_str = cJSON_Print(tree); cJSON_Delete(tree); - fptr = fopen(filename, "wb"); +#ifdef WIN32 + fptr = mosquitto__fopen(filename, "wb", true); +#else + int fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0640); + if(fd < 0){ + free(json_str); + fprintf(stderr, "dynsec init: Unable to open '%s' for writing (%s).\n", filename, strerror(errno)); + return -1; + } + fptr = fdopen(fd, "wb"); +#endif if(fptr){ fprintf(fptr, "%s", json_str); free(json_str); diff --git a/apps/mosquitto_ctrl/options.c b/apps/mosquitto_ctrl/options.c index 592d9e8c..ff32eafb 100644 --- a/apps/mosquitto_ctrl/options.c +++ b/apps/mosquitto_ctrl/options.c @@ -593,6 +593,11 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) return 1; } #ifdef WITH_TLS + if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ + fprintf(stderr, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); + mosquitto_lib_cleanup(); + return 1; + } if(cfg->cafile || cfg->capath){ rc = mosquitto_tls_set(mosq, cfg->cafile, cfg->capath, cfg->certfile, cfg->keyfile, NULL); if(rc){ @@ -615,11 +620,6 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) mosquitto_lib_cleanup(); return 1; } - if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ - fprintf(stderr, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); - mosquitto_lib_cleanup(); - return 1; - } if(cfg->tls_engine_kpass_sha1 && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE_KPASS_SHA1, cfg->tls_engine_kpass_sha1)){ fprintf(stderr, "Error: Problem setting TLS engine key pass sha, is it a 40 character hex string?\n"); mosquitto_lib_cleanup(); diff --git a/apps/mosquitto_passwd/mosquitto_passwd.c b/apps/mosquitto_passwd/mosquitto_passwd.c index f5b2470e..262f89cf 100644 --- a/apps/mosquitto_passwd/mosquitto_passwd.c +++ b/apps/mosquitto_passwd/mosquitto_passwd.c @@ -370,15 +370,27 @@ static int copy_contents(FILE *src, FILE *dest) return 0; } -static int create_backup(const char *backup_file, FILE *fptr) +static int create_backup(char *backup_file, FILE *fptr) { FILE *fbackup; - fbackup = fopen(backup_file, "wt"); +#ifdef WIN32 + fbackup = mosquitto__fopen(backup_file, "wt", true); +#else + int fd; + umask(077); + fd = mkstemp(backup_file); + if(fd < 0){ + fprintf(stderr, "Error creating backup password file \"%s\", not continuing.\n", backup_file); + return 1; + } + fbackup = fdopen(fd, "wt"); +#endif if(!fbackup){ fprintf(stderr, "Error creating backup password file \"%s\", not continuing.\n", backup_file); return 1; } + if(copy_contents(fptr, fbackup)){ fprintf(stderr, "Error copying data to backup password file \"%s\", not continuing.\n", backup_file); fclose(fbackup); @@ -599,7 +611,7 @@ int main(int argc, char *argv[]) } password_cmd = password; } - fptr = fopen(password_file, "wt"); + fptr = mosquitto__fopen(password_file, "wt", true); if(!fptr){ fprintf(stderr, "Error: Unable to open file %s for writing. %s.\n", password_file, strerror(errno)); free(password_file); @@ -610,20 +622,20 @@ int main(int argc, char *argv[]) fclose(fptr); return rc; }else{ - fptr = fopen(password_file, "r+t"); + fptr = mosquitto__fopen(password_file, "r+t", true); if(!fptr){ fprintf(stderr, "Error: Unable to open password file %s. %s.\n", password_file, strerror(errno)); free(password_file); return 1; } - backup_file = malloc((size_t)strlen(password_file)+5); + backup_file = malloc((size_t)strlen(password_file)+strlen(".backup.XXXXXX")); if(!backup_file){ fprintf(stderr, "Error: Out of memory.\n"); free(password_file); return 1; } - snprintf(backup_file, strlen(password_file)+5, "%s.tmp", password_file); + snprintf(backup_file, strlen(password_file)+5, "%s.backup.XXXXXX", password_file); free(password_file); password_file = NULL; diff --git a/client/client_shared.c b/client/client_shared.c index fee029a6..0dd7de44 100644 --- a/client/client_shared.c +++ b/client/client_shared.c @@ -1253,6 +1253,11 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) return 1; } #ifdef WITH_TLS + if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ + err_printf(cfg, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); + mosquitto_lib_cleanup(); + return 1; + } if(cfg->cafile || cfg->capath){ rc = mosquitto_tls_set(mosq, cfg->cafile, cfg->capath, cfg->certfile, cfg->keyfile, NULL); if(rc){ @@ -1289,11 +1294,6 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) mosquitto_lib_cleanup(); return 1; } - if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ - err_printf(cfg, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); - mosquitto_lib_cleanup(); - return 1; - } if(cfg->tls_engine_kpass_sha1 && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE_KPASS_SHA1, cfg->tls_engine_kpass_sha1)){ err_printf(cfg, "Error: Problem setting TLS engine key pass sha, is it a 40 character hex string?\n"); mosquitto_lib_cleanup(); diff --git a/client/sub_client_output.c b/client/sub_client_output.c index acefa167..8bf7cdb5 100644 --- a/client/sub_client_output.c +++ b/client/sub_client_output.c @@ -210,7 +210,7 @@ static int json_print_properties(cJSON *root, const mosquitto_property *properti break; case MQTT_PROP_TOPIC_ALIAS: - mosquitto_property_read_int16(prop, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, &i16value, false); + mosquitto_property_read_int16(prop, MQTT_PROP_TOPIC_ALIAS, &i16value, false); tmp = cJSON_CreateNumber(i16value); break; diff --git a/config.mk b/config.mk index 73daefdf..d64636bc 100644 --- a/config.mk +++ b/config.mk @@ -127,7 +127,7 @@ WITH_XTREPORT=no # Also bump lib/mosquitto.h, CMakeLists.txt, # installer/mosquitto.nsi, installer/mosquitto64.nsi -VERSION=2.0.15 +VERSION=2.0.16 # Client library SO version. Bump if incompatible API/ABI changes are made. SOVERSION=1 diff --git a/include/mosquitto.h b/include/mosquitto.h index 2d34976c..6df18918 100644 --- a/include/mosquitto.h +++ b/include/mosquitto.h @@ -66,7 +66,7 @@ extern "C" { #define LIBMOSQUITTO_MAJOR 2 #define LIBMOSQUITTO_MINOR 0 -#define LIBMOSQUITTO_REVISION 15 +#define LIBMOSQUITTO_REVISION 16 /* LIBMOSQUITTO_VERSION_NUMBER looks like 1002001 for e.g. version 1.2.1. */ #define LIBMOSQUITTO_VERSION_NUMBER (LIBMOSQUITTO_MAJOR*1000000+LIBMOSQUITTO_MINOR*1000+LIBMOSQUITTO_REVISION) @@ -497,8 +497,8 @@ libmosq_EXPORT int mosquitto_username_pw_set(struct mosquitto *mosq, const char * mosq - a valid mosquitto instance. * host - the hostname or ip address of the broker to connect to. * port - the network port to connect to. Usually 1883. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * * Returns: @@ -529,8 +529,8 @@ libmosq_EXPORT int mosquitto_connect(struct mosquitto *mosq, const char *host, i * mosq - a valid mosquitto instance. * host - the hostname or ip address of the broker to connect to. * port - the network port to connect to. Usually 1883. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * bind_address - the hostname or ip address of the local network interface to * bind to. If you do not want to bind to a specific interface, @@ -573,8 +573,8 @@ libmosq_EXPORT int mosquitto_connect_bind(struct mosquitto *mosq, const char *ho * mosq - a valid mosquitto instance. * host - the hostname or ip address of the broker to connect to. * port - the network port to connect to. Usually 1883. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * bind_address - the hostname or ip address of the local network interface to * bind to. If you do not want to bind to a specific interface, @@ -614,8 +614,8 @@ libmosq_EXPORT int mosquitto_connect_bind_v5(struct mosquitto *mosq, const char * mosq - a valid mosquitto instance. * host - the hostname or ip address of the broker to connect to. * port - the network port to connect to. Usually 1883. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * * Returns: @@ -649,8 +649,8 @@ libmosq_EXPORT int mosquitto_connect_async(struct mosquitto *mosq, const char *h * mosq - a valid mosquitto instance. * host - the hostname or ip address of the broker to connect to. * port - the network port to connect to. Usually 1883. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * bind_address - the hostname or ip address of the local network interface to * bind to. If you do not want to bind to a specific interface, @@ -688,8 +688,8 @@ libmosq_EXPORT int mosquitto_connect_bind_async(struct mosquitto *mosq, const ch * Parameters: * mosq - a valid mosquitto instance. * host - the hostname to search for an SRV record. - * keepalive - the number of seconds after which the broker should send a PING - * message to the client if no other messages have been exchanged + * keepalive - the number of seconds after which the client should send a PING + * message to the broker if no other messages have been exchanged * in that time. * bind_address - the hostname or ip address of the local network interface to * bind to. If you do not want to bind to a specific interface, diff --git a/installer/mosquitto.nsi b/installer/mosquitto.nsi index 5450fae0..39659637 100644 --- a/installer/mosquitto.nsi +++ b/installer/mosquitto.nsi @@ -9,7 +9,7 @@ !define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' Name "Eclipse Mosquitto" -!define VERSION 2.0.15 +!define VERSION 2.0.16 OutFile "mosquitto-${VERSION}-install-windows-x86.exe" InstallDir "$PROGRAMFILES\mosquitto" diff --git a/installer/mosquitto64.nsi b/installer/mosquitto64.nsi index 71d0aef9..5b7086bb 100644 --- a/installer/mosquitto64.nsi +++ b/installer/mosquitto64.nsi @@ -9,7 +9,7 @@ !define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' Name "Eclipse Mosquitto" -!define VERSION 2.0.15 +!define VERSION 2.0.16 OutFile "mosquitto-${VERSION}-install-windows-x64.exe" !include "x64.nsh" diff --git a/lib/handle_pubackcomp.c b/lib/handle_pubackcomp.c index b2dcf7ee..4568bb40 100644 --- a/lib/handle_pubackcomp.c +++ b/lib/handle_pubackcomp.c @@ -100,6 +100,7 @@ int handle__pubackcomp(struct mosquitto *mosq, const char *type) && reason_code != MQTT_RC_PAYLOAD_FORMAT_INVALID ){ + mosquitto_property_free_all(&properties); return MOSQ_ERR_PROTOCOL; } }else{ @@ -107,14 +108,13 @@ int handle__pubackcomp(struct mosquitto *mosq, const char *type) && reason_code != MQTT_RC_PACKET_ID_NOT_FOUND ){ + mosquitto_property_free_all(&properties); return MOSQ_ERR_PROTOCOL; } } } if(mosq->in_packet.pos < mosq->in_packet.remaining_length){ -#ifdef WITH_BROKER mosquitto_property_free_all(&properties); -#endif return MOSQ_ERR_MALFORMED_PACKET; } diff --git a/lib/loop.c b/lib/loop.c index 965294f0..0277a1d3 100644 --- a/lib/loop.c +++ b/lib/loop.c @@ -63,20 +63,22 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets) if(mosq->sock != INVALID_SOCKET){ maxfd = mosq->sock; FD_SET(mosq->sock, &readfds); - pthread_mutex_lock(&mosq->current_out_packet_mutex); - pthread_mutex_lock(&mosq->out_packet_mutex); - if(mosq->out_packet || mosq->current_out_packet){ + if(mosq->want_write){ FD_SET(mosq->sock, &writefds); - } + }else{ #ifdef WITH_TLS - if(mosq->ssl){ - if(mosq->want_write){ - FD_SET(mosq->sock, &writefds); + if(mosq->ssl == NULL || SSL_is_init_finished(mosq->ssl)) +#endif + { + pthread_mutex_lock(&mosq->current_out_packet_mutex); + pthread_mutex_lock(&mosq->out_packet_mutex); + if(mosq->out_packet || mosq->current_out_packet){ + FD_SET(mosq->sock, &writefds); + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + pthread_mutex_unlock(&mosq->current_out_packet_mutex); } } -#endif - pthread_mutex_unlock(&mosq->out_packet_mutex); - pthread_mutex_unlock(&mosq->current_out_packet_mutex); }else{ #ifdef WITH_SRV if(mosq->achan){ diff --git a/lib/misc_mosq.c b/lib/misc_mosq.c index 5004125a..3ee6fc79 100644 --- a/lib/misc_mosq.c +++ b/lib/misc_mosq.c @@ -22,6 +22,8 @@ Contributors: #include "config.h" #include +#include +#include #include #include #include @@ -33,8 +35,12 @@ Contributors: # include # include # include +# define PATH_MAX MAX_PATH #else # include +# include +# include +# include #endif #include "misc_mosq.h" @@ -126,30 +132,87 @@ FILE *mosquitto__fopen(const char *path, const char *mode, bool restrict_read) } } #else - if(mode[0] == 'r'){ - struct stat statbuf; - if(stat(path, &statbuf) < 0){ - return NULL; - } - - if(!S_ISREG(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: %s is not a file.", path); - return NULL; - } - } + FILE *fptr; + struct stat statbuf; if (restrict_read) { - FILE *fptr; mode_t old_mask; old_mask = umask(0077); fptr = fopen(path, mode); umask(old_mask); - - return fptr; }else{ - return fopen(path, mode); + fptr = fopen(path, mode); } + if(!fptr) return NULL; + + if(fstat(fileno(fptr), &statbuf) < 0){ + fclose(fptr); + return NULL; + } + + if(restrict_read){ + if(statbuf.st_mode & S_IRWXO){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s has world readable permissions. Future versions will refuse to load this file.", + path); +#if 0 + return NULL; +#endif + } + if(statbuf.st_uid != getuid()){ + char buf[4096]; + struct passwd pw, *result; + + getpwuid_r(getuid(), &pw, buf, sizeof(buf), &result); + if(result){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s owner is not %s. Future versions will refuse to load this file.", + path, result->pw_name); + } +#if 0 + // Future version + return NULL; +#endif + } + if(statbuf.st_gid != getgid()){ + char buf[4096]; + struct group grp, *result; + + getgrgid_r(getgid(), &grp, buf, sizeof(buf), &result); + if(result){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s group is not %s. Future versions will refuse to load this file.", + path, result->gr_name); + } +#if 0 + // Future version + return NULL +#endif + } + } + + + if(!S_ISREG(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_ERR, "Error: %s is not a file.", path); +#endif + fclose(fptr); + return NULL; + } + return fptr; #endif } diff --git a/lib/mosquitto.c b/lib/mosquitto.c index 27a44c15..a83d43a7 100644 --- a/lib/mosquitto.c +++ b/lib/mosquitto.c @@ -61,8 +61,11 @@ int mosquitto_lib_init(void) srand((unsigned int)GetTickCount64()); #elif _POSIX_TIMERS>0 && defined(_POSIX_MONOTONIC_CLOCK) struct timespec tp; - +#ifdef CLOCK_BOOTTIME + clock_gettime(CLOCK_BOOTTIME, &tp); +#else clock_gettime(CLOCK_MONOTONIC, &tp); +#endif srand((unsigned int)tp.tv_nsec); #elif defined(__APPLE__) uint64_t ticks; @@ -329,18 +332,7 @@ int mosquitto_socket(struct mosquitto *mosq) bool mosquitto_want_write(struct mosquitto *mosq) { - bool result = false; - if(mosq->out_packet || mosq->current_out_packet){ - result = true; - } -#ifdef WITH_TLS - if(mosq->ssl){ - if (mosq->want_write) { - result = true; - } - } -#endif - return result; + return mosq->out_packet || mosq->current_out_packet || mosq->want_write; } diff --git a/lib/net_mosq.c b/lib/net_mosq.c index 80d9195b..b8d14a02 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -684,7 +684,7 @@ static int net__init_ssl_ctx(struct mosquitto *mosq) #endif if(!mosq->tls_version){ - SSL_CTX_set_options(mosq->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); + SSL_CTX_set_options(mosq->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); #ifdef SSL_OP_NO_TLSv1_3 }else if(!strcmp(mosq->tls_version, "tlsv1.3")){ SSL_CTX_set_options(mosq->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2); diff --git a/lib/options.c b/lib/options.c index fa7386c2..bf102112 100644 --- a/lib/options.c +++ b/lib/options.c @@ -179,19 +179,21 @@ int mosquitto_tls_set(struct mosquitto *mosq, const char *cafile, const char *ca mosquitto__free(mosq->tls_keyfile); mosq->tls_keyfile = NULL; if(keyfile){ - fptr = mosquitto__fopen(keyfile, "rt", false); - if(fptr){ - fclose(fptr); - }else{ - mosquitto__free(mosq->tls_cafile); - mosq->tls_cafile = NULL; + if(mosq->tls_keyform == mosq_k_pem){ + fptr = mosquitto__fopen(keyfile, "rt", false); + if(fptr){ + fclose(fptr); + }else{ + mosquitto__free(mosq->tls_cafile); + mosq->tls_cafile = NULL; - mosquitto__free(mosq->tls_capath); - mosq->tls_capath = NULL; + mosquitto__free(mosq->tls_capath); + mosq->tls_capath = NULL; - mosquitto__free(mosq->tls_certfile); - mosq->tls_certfile = NULL; - return MOSQ_ERR_INVAL; + mosquitto__free(mosq->tls_certfile); + mosq->tls_certfile = NULL; + return MOSQ_ERR_INVAL; + } } mosq->tls_keyfile = mosquitto__strdup(keyfile); if(!mosq->tls_keyfile){ @@ -228,19 +230,23 @@ int mosquitto_tls_opts_set(struct mosquitto *mosq, int cert_reqs, const char *tl || !strcasecmp(tls_version, "tlsv1.2") || !strcasecmp(tls_version, "tlsv1.1")){ + mosquitto__free(mosq->tls_version); mosq->tls_version = mosquitto__strdup(tls_version); if(!mosq->tls_version) return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_INVAL; } }else{ + mosquitto__free(mosq->tls_version); mosq->tls_version = mosquitto__strdup("tlsv1.2"); if(!mosq->tls_version) return MOSQ_ERR_NOMEM; } if(ciphers){ + mosquitto__free(mosq->tls_ciphers); mosq->tls_ciphers = mosquitto__strdup(ciphers); if(!mosq->tls_ciphers) return MOSQ_ERR_NOMEM; }else{ + mosquitto__free(mosq->tls_ciphers); mosq->tls_ciphers = NULL; } @@ -286,6 +292,11 @@ int mosquitto_string_option(struct mosquitto *mosq, enum mosq_opt_t option, cons #if defined(WITH_TLS) && !defined(OPENSSL_NO_ENGINE) mosquitto__free(mosq->tls_engine); if(value){ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* The "Dynamic" OpenSSL engine is not initialized by default but + is required by ENGINE_by_id() to find dynamically loadable engines */ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_DYNAMIC, NULL); +#endif eng = ENGINE_by_id(value); if(!eng){ return MOSQ_ERR_INVAL; diff --git a/lib/packet_mosq.c b/lib/packet_mosq.c index 80f47168..8c06b942 100644 --- a/lib/packet_mosq.c +++ b/lib/packet_mosq.c @@ -152,6 +152,21 @@ int packet__queue(struct mosquitto *mosq, struct mosquitto__packet *packet) packet->next = NULL; pthread_mutex_lock(&mosq->out_packet_mutex); + +#ifdef WITH_BROKER + if(mosq->out_packet_count >= db.config->max_queued_messages){ + mosquitto__free(packet); + if(mosq->is_dropping == false){ + mosq->is_dropping = true; + log__printf(NULL, MOSQ_LOG_NOTICE, + "Outgoing messages are being dropped for client %s.", + mosq->id); + } + G_MSGS_DROPPED_INC(); + return MOSQ_ERR_SUCCESS; + } +#endif + if(mosq->out_packet){ mosq->out_packet_last->next = packet; }else{ @@ -268,6 +283,8 @@ int packet__write(struct mosquitto *mosq) return MOSQ_ERR_CONN_LOST; case COMPAT_EINTR: return MOSQ_ERR_SUCCESS; + case EPROTO: + return MOSQ_ERR_TLS; default: return MOSQ_ERR_ERRNO; } @@ -376,7 +393,7 @@ int packet__read(struct mosquitto *mosq) #ifdef WITH_BROKER G_BYTES_RECEIVED_INC(1); /* Clients must send CONNECT as their first command. */ - if(!(mosq->bridge) && state == mosq_cs_connected && (byte&0xF0) != CMD_CONNECT){ + if(!(mosq->bridge) && state == mosq_cs_new && (byte&0xF0) != CMD_CONNECT){ return MOSQ_ERR_PROTOCOL; } #endif diff --git a/lib/property_mosq.c b/lib/property_mosq.c index 71c49737..0b27c297 100644 --- a/lib/property_mosq.c +++ b/lib/property_mosq.c @@ -141,7 +141,9 @@ static int property__read(struct mosquitto__packet *packet, uint32_t *len, mosqu break; default: +#ifdef WITH_BROKER log__printf(NULL, MOSQ_LOG_DEBUG, "Unsupported property type: %d", property_identifier); +#endif return MOSQ_ERR_MALFORMED_PACKET; } @@ -415,7 +417,9 @@ static int property__write(struct mosquitto__packet *packet, const mosquitto_pro break; default: +#ifdef WITH_BROKER log__printf(NULL, MOSQ_LOG_DEBUG, "Unsupported property type: %d", property->identifier); +#endif return MOSQ_ERR_INVAL; } @@ -1248,7 +1252,7 @@ int mosquitto_property_copy_all(mosquitto_property **dest, const mosquitto_prope case MQTT_PROP_SERVER_REFERENCE: case MQTT_PROP_REASON_STRING: pnew->value.s.len = src->value.s.len; - pnew->value.s.v = strdup(src->value.s.v); + pnew->value.s.v = src->value.s.v ? strdup(src->value.s.v) : (char*)calloc(1,1); if(!pnew->value.s.v){ mosquitto_property_free_all(dest); return MOSQ_ERR_NOMEM; @@ -1268,14 +1272,14 @@ int mosquitto_property_copy_all(mosquitto_property **dest, const mosquitto_prope case MQTT_PROP_USER_PROPERTY: pnew->value.s.len = src->value.s.len; - pnew->value.s.v = strdup(src->value.s.v); + pnew->value.s.v = src->value.s.v ? strdup(src->value.s.v) : (char*)calloc(1,1); if(!pnew->value.s.v){ mosquitto_property_free_all(dest); return MOSQ_ERR_NOMEM; } pnew->name.len = src->name.len; - pnew->name.v = strdup(src->name.v); + pnew->name.v = src->name.v ? strdup(src->name.v) : (char*)calloc(1,1); if(!pnew->name.v){ mosquitto_property_free_all(dest); return MOSQ_ERR_NOMEM; diff --git a/lib/send_publish.c b/lib/send_publish.c index 1b5ffdaa..be52474b 100644 --- a/lib/send_publish.c +++ b/lib/send_publish.c @@ -177,7 +177,7 @@ int send__real_publish(struct mosquitto *mosq, uint16_t mid, const char *topic, #ifdef WITH_BROKER log__printf(NULL, MOSQ_LOG_NOTICE, "Dropping too large outgoing PUBLISH for %s (%d bytes)", SAFE_PRINT(mosq->id), packetlen); #else - log__printf(NULL, MOSQ_LOG_NOTICE, "Dropping too large outgoing PUBLISH (%d bytes)", packetlen); + log__printf(mosq, MOSQ_LOG_NOTICE, "Dropping too large outgoing PUBLISH (%d bytes)", packetlen); #endif return MOSQ_ERR_OVERSIZE_PACKET; } diff --git a/lib/strings_mosq.c b/lib/strings_mosq.c index 419294a6..2516b06b 100644 --- a/lib/strings_mosq.c +++ b/lib/strings_mosq.c @@ -75,6 +75,10 @@ const char *mosquitto_strerror(int mosq_errno) return "Proxy error."; case MOSQ_ERR_MALFORMED_UTF8: return "Malformed UTF-8"; + case MOSQ_ERR_KEEPALIVE: + return "Keepalive exceeded"; + case MOSQ_ERR_LOOKUP: + return "DNS Lookup failed"; case MOSQ_ERR_DUPLICATE_PROPERTY: return "Duplicate property in property list"; case MOSQ_ERR_TLS_HANDSHAKE: diff --git a/lib/time_mosq.c b/lib/time_mosq.c index 3a9362c2..110322a4 100644 --- a/lib/time_mosq.c +++ b/lib/time_mosq.c @@ -43,7 +43,11 @@ time_t mosquitto_time(void) #elif _POSIX_TIMERS>0 && defined(_POSIX_MONOTONIC_CLOCK) struct timespec tp; +#ifdef CLOCK_BOOTTIME + clock_gettime(CLOCK_BOOTTIME, &tp); +#else clock_gettime(CLOCK_MONOTONIC, &tp); +#endif return tp.tv_sec; #elif defined(__APPLE__) static mach_timebase_info_data_t tb; diff --git a/lib/tls_mosq.c b/lib/tls_mosq.c index 940df073..f85379ed 100644 --- a/lib/tls_mosq.c +++ b/lib/tls_mosq.c @@ -105,6 +105,17 @@ static int mosquitto__cmp_hostname_wildcard(char *certname, const char *hostname break; } } + len = strlen(hostname); + int dotcount = 0; + for(i=0; iServer Generate a server key. - openssl genrsa -des3 -out server.key 2048 + openssl genrsa -aes256 -out server.key 2048 Generate a server key without encryption. @@ -71,7 +71,7 @@ Client Generate a client key. - openssl genrsa -des3 -out client.key 2048 + openssl genrsa -aes256 -out client.key 2048 Generate a certificate signing request to send to the CA. diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index 35e016c8..90509fd4 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -1391,9 +1391,12 @@ openssl dhparam -out dhparam.pem 2048 file path - Path to the PEM encoded server key. This - option and must be present - to enable certificate based TLS encryption. + If equals "pem" this is the + path to the PEM encoded server key. This option + and must be present + to enable certificate based TLS encryption. If + is "engine" this represents + the engine handle of the private key. The private key pointed to by this option will be @@ -1458,7 +1461,7 @@ openssl dhparam -out dhparam.pem 2048 tlsv1.3, tlsv1.2 and tlsv1.1. If left unset, - the default of allowing TLS v1.3 and v1.2. + the default allows TLS v1.3 and v1.2. In Mosquitto version 1.6.x and earlier, this option set the only TLS protocol version that was allowed, rather than the minimum. diff --git a/man/mosquitto_rr.1.xml b/man/mosquitto_rr.1.xml index 7e45b683..2563a73c 100644 --- a/man/mosquitto_rr.1.xml +++ b/man/mosquitto_rr.1.xml @@ -639,6 +639,16 @@ Defaults to . + + + + Provide a timeout as an integer number of seconds. + mosquitto_sub will stop processing messages and + disconnect after this number of seconds has + passed. The timeout starts just after the client has + connected to the broker. + + diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index 6a7c5b38..ee9274e4 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -938,7 +938,6 @@ mosquitto_sub -t 'bbc/#' -T bbc/bbc1 --remove-retained If the payload is not valid JSON, then the error message "Error: Message payload is not valid JSON on topic <topic>" will be printed to stderr. - ISO-8601 format date and time, e.g. 2016-08-10T09:47:38+0100 Unix timestamp with nanoseconds, e.g. 1470818943.786368637 diff --git a/misc/letsencrypt/mosquitto-copy.sh b/misc/letsencrypt/mosquitto-copy.sh index ef3d3766..3c19db7e 100755 --- a/misc/letsencrypt/mosquitto-copy.sh +++ b/misc/letsencrypt/mosquitto-copy.sh @@ -17,17 +17,19 @@ MY_DOMAIN=example.com # Set the directory that the certificates will be copied to. CERTIFICATE_DIR=/etc/mosquitto/certs -if [ "${RENEWED_DOMAINS}" = "${MY_DOMAIN}" ]; then - # Copy new certificate to Mosquitto directory - cp ${RENEWED_LINEAGE}/fullchain.pem ${CERTIFICATE_DIR}/server.pem - cp ${RENEWED_LINEAGE}/privkey.pem ${CERTIFICATE_DIR}/server.key +for D in ${RENEWED_DOMAINS}; do + if [ "${D}" = "${MY_DOMAIN}" ]; then + # Copy new certificate to Mosquitto directory + cp ${RENEWED_LINEAGE}/fullchain.pem ${CERTIFICATE_DIR}/server.pem + cp ${RENEWED_LINEAGE}/privkey.pem ${CERTIFICATE_DIR}/server.key - # Set ownership to Mosquitto - chown mosquitto: ${CERTIFICATE_DIR}/server.pem ${CERTIFICATE_DIR}/server.key + # Set ownership to Mosquitto + chown mosquitto: ${CERTIFICATE_DIR}/server.pem ${CERTIFICATE_DIR}/server.key - # Ensure permissions are restrictive - chmod 0600 ${CERTIFICATE_DIR}/server.pem ${CERTIFICATE_DIR}/server.key + # Ensure permissions are restrictive + chmod 0600 ${CERTIFICATE_DIR}/server.pem ${CERTIFICATE_DIR}/server.key - # Tell Mosquitto to reload certificates and configuration - pkill -HUP -x mosquitto -fi + # Tell Mosquitto to reload certificates and configuration + pkill -HUP -x mosquitto + fi +done diff --git a/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c b/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c index 191cf710..323a0d81 100644 --- a/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c +++ b/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c @@ -106,5 +106,5 @@ int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int op UNUSED(opts); UNUSED(opt_count); - return mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_MESSAGE, basic_auth_callback, NULL); + return mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_BASIC_AUTH, basic_auth_callback, NULL); } diff --git a/plugins/dynamic-security/README.md b/plugins/dynamic-security/README.md index 6949d59c..88772514 100644 --- a/plugins/dynamic-security/README.md +++ b/plugins/dynamic-security/README.md @@ -61,7 +61,7 @@ Command: { "commands":[ { - "command": "getDefaultACLAccess", + "command": "getDefaultACLAccess" } ] } @@ -244,7 +244,7 @@ Command: mosquitto_ctrl example: ``` -mosquitto_ctrl dynsec setClientPassword username password +mosquitto_ctrl dynsec setClientId username clientId ``` ## Set Client Password @@ -523,7 +523,7 @@ Command: { "commands":[ { - "command": "getAnonymousGroup", + "command": "getAnonymousGroup" } ] } diff --git a/plugins/dynamic-security/auth.c b/plugins/dynamic-security/auth.c index 1f62e1fb..03e4f6b9 100644 --- a/plugins/dynamic-security/auth.c +++ b/plugins/dynamic-security/auth.c @@ -163,9 +163,7 @@ static int memcmp_const(const void *a, const void *b, size_t len) if(!a || !b) return 1; for(i=0; iusername = mosquitto_strdup(jtmp->valuestring); + + client->username = mosquitto_strdup(username); if(client->username == NULL){ mosquitto_free(client); continue; } - jtmp = cJSON_GetObjectItem(j_client, "disabled"); - if(jtmp && cJSON_IsBool(jtmp)){ - client->disabled = cJSON_IsTrue(jtmp); + bool disabled; + if(json_get_bool(j_client, "disabled", &disabled, false, false) == MOSQ_ERR_SUCCESS){ + client->disabled = disabled; } /* Salt */ - j_salt = cJSON_GetObjectItem(j_client, "salt"); - j_password = cJSON_GetObjectItem(j_client, "password"); - j_iterations = cJSON_GetObjectItem(j_client, "iterations"); + char *salt, *password; + int iterations; + json_get_string(j_client, "salt", &salt, false); + json_get_string(j_client, "password", &password, false); + json_get_int(j_client, "iterations", &iterations, false, -1); - if(j_salt && cJSON_IsString(j_salt) - && j_password && cJSON_IsString(j_password) - && j_iterations && cJSON_IsNumber(j_iterations)){ + if(salt && password && iterations > 0){ + client->pw.iterations = iterations; - iterations = (int)j_iterations->valuedouble; - if(iterations < 1){ - mosquitto_free(client->username); - mosquitto_free(client); - continue; - }else{ - client->pw.iterations = iterations; - } - - if(dynsec_auth__base64_decode(j_salt->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS + if(dynsec_auth__base64_decode(salt, &buf, &buf_len) != MOSQ_ERR_SUCCESS || buf_len != sizeof(client->pw.salt)){ mosquitto_free(client->username); @@ -176,7 +173,7 @@ int dynsec_clients__config_load(cJSON *tree) memcpy(client->pw.salt, buf, (size_t)buf_len); mosquitto_free(buf); - if(dynsec_auth__base64_decode(j_password->valuestring, &buf, &buf_len) != MOSQ_ERR_SUCCESS + if(dynsec_auth__base64_decode(password, &buf, &buf_len) != MOSQ_ERR_SUCCESS || buf_len != sizeof(client->pw.password_hash)){ mosquitto_free(client->username); @@ -191,9 +188,10 @@ int dynsec_clients__config_load(cJSON *tree) } /* Client id */ - jtmp = cJSON_GetObjectItem(j_client, "clientid"); - if(jtmp != NULL && cJSON_IsString(jtmp)){ - client->clientid = mosquitto_strdup(jtmp->valuestring); + char *clientid; + json_get_string(j_client, "clientid", &clientid, false); + if(clientid){ + client->clientid = mosquitto_strdup(clientid); if(client->clientid == NULL){ mosquitto_free(client->username); mosquitto_free(client); @@ -202,9 +200,10 @@ int dynsec_clients__config_load(cJSON *tree) } /* Text name */ - jtmp = cJSON_GetObjectItem(j_client, "textname"); - if(jtmp != NULL && cJSON_IsString(jtmp)){ - client->text_name = mosquitto_strdup(jtmp->valuestring); + char *textname; + json_get_string(j_client, "textname", &textname, false); + if(textname){ + client->text_name = mosquitto_strdup(textname); if(client->text_name == NULL){ mosquitto_free(client->clientid); mosquitto_free(client->username); @@ -214,9 +213,10 @@ int dynsec_clients__config_load(cJSON *tree) } /* Text description */ - jtmp = cJSON_GetObjectItem(j_client, "textdescription"); - if(jtmp != NULL && cJSON_IsString(jtmp)){ - client->text_description = mosquitto_strdup(jtmp->valuestring); + char *textdescription; + json_get_string(j_client, "textdescription", &textdescription, false); + if(textdescription){ + client->text_description = mosquitto_strdup(textdescription); if(client->text_description == NULL){ mosquitto_free(client->text_name); mosquitto_free(client->clientid); @@ -231,10 +231,11 @@ int dynsec_clients__config_load(cJSON *tree) if(j_roles && cJSON_IsArray(j_roles)){ cJSON_ArrayForEach(j_role, j_roles){ if(cJSON_IsObject(j_role)){ - jtmp = cJSON_GetObjectItem(j_role, "rolename"); - if(jtmp && cJSON_IsString(jtmp)){ + char *rolename; + json_get_string(j_role, "rolename", &rolename, false); + if(rolename){ json_get_int(j_role, "priority", &priority, true, -1); - role = dynsec_roles__find(jtmp->valuestring); + role = dynsec_roles__find(rolename); dynsec_rolelist__client_add(client, role, priority); } } @@ -326,7 +327,7 @@ int dynsec_clients__process_create(cJSON *j_responses, struct mosquitto *context char *text_name, *text_description; struct dynsec__client *client; int rc; - cJSON *j_groups, *j_group, *jtmp; + cJSON *j_groups, *j_group; int priority; const char *admin_clientid, *admin_username; @@ -438,10 +439,11 @@ int dynsec_clients__process_create(cJSON *j_responses, struct mosquitto *context if(j_groups && cJSON_IsArray(j_groups)){ cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group)){ - jtmp = cJSON_GetObjectItem(j_group, "groupname"); - if(jtmp && cJSON_IsString(jtmp)){ + char *groupname; + json_get_string(j_group, "groupname", &groupname, false); + if(groupname){ json_get_int(j_group, "priority", &priority, true, -1); - rc = dynsec_groups__add_client(username, jtmp->valuestring, priority, false); + rc = dynsec_groups__add_client(username, groupname, priority, false); if(rc == ERR_GROUP_NOT_FOUND){ dynsec__command_reply(j_responses, context, "createClient", "Group not found", correlation_data); client__free_item(client); @@ -730,7 +732,7 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context char *str; int rc; int priority; - cJSON *j_group, *j_groups, *jtmp; + cJSON *j_group, *j_groups; const char *admin_clientid, *admin_username; if(json_get_string(command, "username", &username, false) != MOSQ_ERR_SUCCESS){ @@ -812,9 +814,10 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context /* Iterate through list to check all groups are valid */ cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group)){ - jtmp = cJSON_GetObjectItem(j_group, "groupname"); - if(jtmp && cJSON_IsString(jtmp)){ - group = dynsec_groups__find(jtmp->valuestring); + char *groupname; + json_get_string(j_group, "groupname", &groupname, false); + if(groupname){ + group = dynsec_groups__find(groupname); if(group == NULL){ dynsec__command_reply(j_responses, context, "modifyClient", "'groups' contains an object with a 'groupname' that does not exist", correlation_data); rc = MOSQ_ERR_INVAL; @@ -831,10 +834,11 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context dynsec__remove_client_from_all_groups(username); cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group)){ - jtmp = cJSON_GetObjectItem(j_group, "groupname"); - if(jtmp && cJSON_IsString(jtmp)){ + char *groupname; + json_get_string(j_group, "groupname", &groupname, false); + if(groupname){ json_get_int(j_group, "priority", &priority, true, -1); - dynsec_groups__add_client(username, jtmp->valuestring, priority, false); + dynsec_groups__add_client(username, groupname, priority, false); } } } diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index f26a2ba5..16fa76f3 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -195,12 +195,12 @@ void dynsec_groups__cleanup(void) int dynsec_groups__config_load(cJSON *tree) { cJSON *j_groups, *j_group; - cJSON *j_clientlist, *j_client, *j_username; - cJSON *j_roles, *j_role, *j_rolename; + cJSON *j_clientlist, *j_client; + cJSON *j_roles, *j_role; struct dynsec__group *group; struct dynsec__role *role; - char *str; + char *groupname; int priority; j_groups = cJSON_GetObjectItem(tree, "groups"); @@ -214,26 +214,31 @@ int dynsec_groups__config_load(cJSON *tree) cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group) == true){ + /* Group name */ + if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ + continue; + } + group = dynsec_groups__find(groupname); + if(group){ + continue; + } + group = mosquitto_calloc(1, sizeof(struct dynsec__group)); if(group == NULL){ return MOSQ_ERR_NOMEM; } - /* Group name */ - if(json_get_string(j_group, "groupname", &str, false) != MOSQ_ERR_SUCCESS){ - mosquitto_free(group); - continue; - } - group->groupname = strdup(str); + group->groupname = strdup(groupname); if(group->groupname == NULL){ mosquitto_free(group); continue; } /* Text name */ - if(json_get_string(j_group, "textname", &str, false) == MOSQ_ERR_SUCCESS){ - if(str){ - group->text_name = strdup(str); + char *textname; + if(json_get_string(j_group, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ + if(textname){ + group->text_name = strdup(textname); if(group->text_name == NULL){ mosquitto_free(group->groupname); mosquitto_free(group); @@ -243,9 +248,10 @@ int dynsec_groups__config_load(cJSON *tree) } /* Text description */ - if(json_get_string(j_group, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){ - if(str){ - group->text_description = strdup(str); + char *textdescription; + if(json_get_string(j_group, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ + if(textdescription){ + group->text_description = strdup(textdescription); if(group->text_description == NULL){ mosquitto_free(group->text_name); mosquitto_free(group->groupname); @@ -260,10 +266,11 @@ int dynsec_groups__config_load(cJSON *tree) if(j_roles && cJSON_IsArray(j_roles)){ cJSON_ArrayForEach(j_role, j_roles){ if(cJSON_IsObject(j_role)){ - j_rolename = cJSON_GetObjectItem(j_role, "rolename"); - if(j_rolename && cJSON_IsString(j_rolename)){ + char *rolename; + json_get_string(j_role, "rolename", &rolename, false); + if(rolename){ json_get_int(j_role, "priority", &priority, true, -1); - role = dynsec_roles__find(j_rolename->valuestring); + role = dynsec_roles__find(rolename); dynsec_rolelist__group_add(group, role, priority); } } @@ -278,10 +285,11 @@ int dynsec_groups__config_load(cJSON *tree) if(j_clientlist && cJSON_IsArray(j_clientlist)){ cJSON_ArrayForEach(j_client, j_clientlist){ if(cJSON_IsObject(j_client)){ - j_username = cJSON_GetObjectItem(j_client, "username"); - if(j_username && cJSON_IsString(j_username)){ + char *username; + json_get_string(j_client, "username", &username, false); + if(username){ json_get_int(j_client, "priority", &priority, true, -1); - dynsec_groups__add_client(j_username->valuestring, group->groupname, priority, false); + dynsec_groups__add_client(username, group->groupname, priority, false); } } } @@ -290,9 +298,9 @@ int dynsec_groups__config_load(cJSON *tree) } HASH_SORT(local_groups, group_cmp); - j_group = cJSON_GetObjectItem(tree, "anonymousGroup"); - if(j_group && cJSON_IsString(j_group)){ - dynsec_anonymous_group = dynsec_groups__find(j_group->valuestring); + json_get_string(tree, "anonymousGroup", &groupname, false); + if(groupname){ + dynsec_anonymous_group = dynsec_groups__find(groupname); } return 0; @@ -922,10 +930,12 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, struct dynsec__group *group = NULL; struct dynsec__rolelist *rolelist = NULL; bool have_text_name = false, have_text_description = false, have_rolelist = false; - char *str; int rc; int priority; - cJSON *j_client, *j_clients, *jtmp; + cJSON *j_client, *j_clients; + char *username; + char *textname; + char *textdescription; const char *admin_clientid, *admin_username; if(json_get_string(command, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ @@ -943,9 +953,9 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, return MOSQ_ERR_INVAL; } - if(json_get_string(command, "textname", &str, false) == MOSQ_ERR_SUCCESS){ + if(json_get_string(command, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ have_text_name = true; - text_name = mosquitto_strdup(str); + text_name = mosquitto_strdup(textname); if(text_name == NULL){ dynsec__command_reply(j_responses, context, "modifyGroup", "Internal error", correlation_data); rc = MOSQ_ERR_NOMEM; @@ -953,9 +963,9 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, } } - if(json_get_string(command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){ + if(json_get_string(command, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ have_text_description = true; - text_description = mosquitto_strdup(str); + text_description = mosquitto_strdup(textdescription); if(text_description == NULL){ dynsec__command_reply(j_responses, context, "modifyGroup", "Internal error", correlation_data); rc = MOSQ_ERR_NOMEM; @@ -989,9 +999,9 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, /* Iterate over array to check clients are valid before proceeding */ cJSON_ArrayForEach(j_client, j_clients){ if(cJSON_IsObject(j_client)){ - jtmp = cJSON_GetObjectItem(j_client, "username"); - if(jtmp && cJSON_IsString(jtmp)){ - client = dynsec_clients__find(jtmp->valuestring); + json_get_string(j_client, "username", &username, false); + if(username){ + client = dynsec_clients__find(username); if(client == NULL){ dynsec__command_reply(j_responses, context, "modifyGroup", "'clients' contains an object with a 'username' that does not exist", correlation_data); rc = MOSQ_ERR_INVAL; @@ -1012,10 +1022,10 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, /* Now we can add the new clients to the group */ cJSON_ArrayForEach(j_client, j_clients){ if(cJSON_IsObject(j_client)){ - jtmp = cJSON_GetObjectItem(j_client, "username"); - if(jtmp && cJSON_IsString(jtmp)){ + json_get_string(j_client, "username", &username, false); + if(username){ json_get_int(j_client, "priority", &priority, true, -1); - dynsec_groups__add_client(jtmp->valuestring, groupname, priority, false); + dynsec_groups__add_client(username, groupname, priority, false); } } } diff --git a/plugins/dynamic-security/plugin.c b/plugins/dynamic-security/plugin.c index 3ff1054a..b5b81fc1 100644 --- a/plugins/dynamic-security/plugin.c +++ b/plugins/dynamic-security/plugin.c @@ -41,6 +41,190 @@ static mosquitto_plugin_id_t *plg_id = NULL; static char *config_file = NULL; struct dynsec__acl_default_access default_access = {false, false, false, false}; +#ifdef WIN32 +# include +# include +# include +# include +# include +# define PATH_MAX MAX_PATH +#else +# include +# include +# include +# include +#endif +/* Temporary - remove in 2.1 */ +FILE *mosquitto__fopen(const char *path, const char *mode, bool restrict_read) +{ +#ifdef WIN32 + char buf[4096]; + int rc; + int flags = 0; + + rc = ExpandEnvironmentStringsA(path, buf, 4096); + if(rc == 0 || rc > 4096){ + return NULL; + }else{ + if (restrict_read) { + HANDLE hfile; + SECURITY_ATTRIBUTES sec; + EXPLICIT_ACCESS_A ea; + PACL pacl = NULL; + char username[UNLEN + 1]; + DWORD ulen = UNLEN; + SECURITY_DESCRIPTOR sd; + DWORD dwCreationDisposition; + int fd; + FILE *fptr; + + switch(mode[0]){ + case 'a': + dwCreationDisposition = OPEN_ALWAYS; + flags = _O_APPEND; + break; + case 'r': + dwCreationDisposition = OPEN_EXISTING; + flags = _O_RDONLY; + break; + case 'w': + dwCreationDisposition = CREATE_ALWAYS; + break; + default: + return NULL; + } + + GetUserNameA(username, &ulen); + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + return NULL; + } + BuildExplicitAccessWithNameA(&ea, username, GENERIC_ALL, SET_ACCESS, NO_INHERITANCE); + if (SetEntriesInAclA(1, &ea, NULL, &pacl) != ERROR_SUCCESS) { + return NULL; + } + if (!SetSecurityDescriptorDacl(&sd, TRUE, pacl, FALSE)) { + LocalFree(pacl); + return NULL; + } + + memset(&sec, 0, sizeof(sec)); + sec.nLength = sizeof(SECURITY_ATTRIBUTES); + sec.bInheritHandle = FALSE; + sec.lpSecurityDescriptor = &sd; + + hfile = CreateFileA(buf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, + &sec, + dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL, + NULL); + + LocalFree(pacl); + + fd = _open_osfhandle((intptr_t)hfile, flags); + if (fd < 0) { + return NULL; + } + + fptr = _fdopen(fd, mode); + if (!fptr) { + _close(fd); + return NULL; + } + if(mode[0] == 'a'){ + fseek(fptr, 0, SEEK_END); + } + return fptr; + + }else { + return fopen(buf, mode); + } + } +#else + FILE *fptr; + struct stat statbuf; + + if (restrict_read) { + mode_t old_mask; + + old_mask = umask(0077); + fptr = fopen(path, mode); + umask(old_mask); + }else{ + fptr = fopen(path, mode); + } + if(!fptr) return NULL; + + if(fstat(fileno(fptr), &statbuf) < 0){ + fclose(fptr); + return NULL; + } + + if(restrict_read){ + if(statbuf.st_mode & S_IRWXO){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s has world readable permissions. Future versions will refuse to load this file.", + path); +#if 0 + return NULL; +#endif + } + if(statbuf.st_uid != getuid()){ + char buf[4096]; + struct passwd pw, *result; + + getpwuid_r(getuid(), &pw, buf, sizeof(buf), &result); + if(result){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s owner is not %s. Future versions will refuse to load this file.", + path, result->pw_name); + } +#if 0 + // Future version + return NULL; +#endif + } + if(statbuf.st_gid != getgid()){ + char buf[4096]; + struct group grp, *result; + + getgrgid_r(getgid(), &grp, buf, sizeof(buf), &result); + if(result){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_WARNING, +#else + fprintf(stderr, +#endif + "Warning: File %s group is not %s. Future versions will refuse to load this file.", + path, result->gr_name); + } +#if 0 + // Future version + return NULL +#endif + } + } + + + if(!S_ISREG(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)){ +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_ERR, "Error: %s is not a file.", path); +#endif + fclose(fptr); + return NULL; + } + return fptr; +#endif +} + + void dynsec__command_reply(cJSON *j_responses, struct mosquitto *context, const char *command, const char *error, const char *correlation_data) { cJSON *j_response; @@ -137,8 +321,7 @@ static int dynsec_control_callback(int event, void *event_data, void *userdata) static int dynsec__process_set_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; + cJSON *j_actions, *j_action; const char *admin_clientid, *admin_username; j_actions = cJSON_GetObjectItem(command, "acls"); @@ -151,24 +334,23 @@ static int dynsec__process_set_default_acl_access(cJSON *j_responses, struct mos admin_username = mosquitto_client_username(context); 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)){ + char *acltype; + bool allow; - allow = cJSON_IsTrue(j_allow); + if(json_get_string(j_action, "acltype", &acltype, false) == MOSQ_ERR_SUCCESS + && json_get_bool(j_action, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS){ - if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_SEND)){ + if(!strcasecmp(acltype, ACL_TYPE_PUB_C_SEND)){ default_access.publish_c_send = allow; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_RECV)){ + }else if(!strcasecmp(acltype, ACL_TYPE_PUB_C_RECV)){ default_access.publish_c_recv = allow; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_GENERIC)){ + }else if(!strcasecmp(acltype, ACL_TYPE_SUB_GENERIC)){ default_access.subscribe = allow; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_GENERIC)){ + }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_GENERIC)){ default_access.unsubscribe = allow; } mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | setDefaultACLAccess | acltype=%s | allow=%s", - admin_clientid, admin_username, j_acltype->valuestring, allow?"true":"false"); + admin_clientid, admin_username, acltype, allow?"true":"false"); } } @@ -292,37 +474,14 @@ int mosquitto_plugin_version(int supported_version_count, const int *supported_v static int dynsec__general_config_load(cJSON *tree) { - cJSON *j_default_access, *jtmp; + cJSON *j_default_access; j_default_access = cJSON_GetObjectItem(tree, "defaultACLAccess"); if(j_default_access && cJSON_IsObject(j_default_access)){ - jtmp = cJSON_GetObjectItem(j_default_access, ACL_TYPE_PUB_C_SEND); - if(jtmp && cJSON_IsBool(jtmp)){ - default_access.publish_c_send = cJSON_IsTrue(jtmp); - }else{ - default_access.publish_c_send = false; - } - - jtmp = cJSON_GetObjectItem(j_default_access, ACL_TYPE_PUB_C_RECV); - if(jtmp && cJSON_IsBool(jtmp)){ - default_access.publish_c_recv = cJSON_IsTrue(jtmp); - }else{ - default_access.publish_c_recv = false; - } - - jtmp = cJSON_GetObjectItem(j_default_access, ACL_TYPE_SUB_GENERIC); - if(jtmp && cJSON_IsBool(jtmp)){ - default_access.subscribe = cJSON_IsTrue(jtmp); - }else{ - default_access.subscribe = false; - } - - jtmp = cJSON_GetObjectItem(j_default_access, ACL_TYPE_UNSUB_GENERIC); - if(jtmp && cJSON_IsBool(jtmp)){ - default_access.unsubscribe = cJSON_IsTrue(jtmp); - }else{ - default_access.unsubscribe = false; - } + json_get_bool(j_default_access, ACL_TYPE_PUB_C_SEND, &default_access.publish_c_send, true, false); + json_get_bool(j_default_access, ACL_TYPE_PUB_C_RECV, &default_access.publish_c_recv, true, false); + json_get_bool(j_default_access, ACL_TYPE_SUB_GENERIC, &default_access.subscribe, true, false); + json_get_bool(j_default_access, ACL_TYPE_UNSUB_GENERIC, &default_access.unsubscribe, true, false); } return MOSQ_ERR_SUCCESS; } @@ -359,7 +518,7 @@ static int dynsec__config_load(void) /* Load from file */ errno = 0; - fptr = fopen(config_file, "rb"); + fptr = mosquitto__fopen(config_file, "rb", true); if(fptr == NULL){ mosquitto_log_printf(MOSQ_LOG_ERR, "Error loading Dynamic security plugin config: File is not readable - check permissions.\n"); return MOSQ_ERR_ERRNO; @@ -451,7 +610,7 @@ void dynsec__config_save(void) json_str_len = strlen(json_str); /* Save to file */ - file_path_len = strlen(config_file) + 1; + file_path_len = strlen(config_file) + strlen(".new") + 1; file_path = mosquitto_malloc(file_path_len); if(file_path == NULL){ mosquitto_free(json_str); @@ -460,7 +619,7 @@ void dynsec__config_save(void) } snprintf(file_path, file_path_len, "%s.new", config_file); - fptr = fopen(file_path, "wt"); + fptr = mosquitto__fopen(file_path, "wt", true); if(fptr == NULL){ mosquitto_free(json_str); mosquitto_free(file_path); diff --git a/plugins/dynamic-security/rolelist.c b/plugins/dynamic-security/rolelist.c index 2bc1f163..63654c6b 100644 --- a/plugins/dynamic-security/rolelist.c +++ b/plugins/dynamic-security/rolelist.c @@ -164,7 +164,7 @@ int dynsec_rolelist__group_add(struct dynsec__group *group, struct dynsec__role int dynsec_rolelist__load_from_json(cJSON *command, struct dynsec__rolelist **rolelist) { - cJSON *j_roles, *j_role, *j_rolename; + cJSON *j_roles, *j_role; int priority; struct dynsec__role *role; @@ -172,10 +172,11 @@ int dynsec_rolelist__load_from_json(cJSON *command, struct dynsec__rolelist **ro if(j_roles){ if(cJSON_IsArray(j_roles)){ cJSON_ArrayForEach(j_role, j_roles){ - j_rolename = cJSON_GetObjectItem(j_role, "rolename"); - if(j_rolename && cJSON_IsString(j_rolename)){ + char *rolename; + json_get_string(j_role, "rolename", &rolename, false); + if(rolename){ json_get_int(j_role, "priority", &priority, true, -1); - role = dynsec_roles__find(j_rolename->valuestring); + role = dynsec_roles__find(rolename); if(role){ dynsec_rolelist__add(rolelist, role, priority); }else{ diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c index 6a393bc0..09b9fe3c 100644 --- a/plugins/dynamic-security/roles.c +++ b/plugins/dynamic-security/roles.c @@ -215,14 +215,24 @@ static int insert_acl_cmp(struct dynsec__acl *a, struct dynsec__acl *b) static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec__acl **acllist) { - cJSON *j_acl, *j_type, *jtmp; + cJSON *j_acl; struct dynsec__acl *acl; cJSON_ArrayForEach(j_acl, j_acls){ - j_type = cJSON_GetObjectItem(j_acl, "acltype"); - if(j_type == NULL || !cJSON_IsString(j_type) || strcasecmp(j_type->valuestring, key) != 0){ + char *acltype; + char *topic; + + json_get_string(j_acl, "acltype", &acltype, false); + json_get_string(j_acl, "topic", &topic, false); + + if(!acltype || strcasecmp(acltype, key) != 0 || !topic){ continue; } + HASH_FIND(hh, *acllist, topic, strlen(topic), acl); + if(acl){ + continue; + } + acl = mosquitto_calloc(1, sizeof(struct dynsec__acl)); if(acl == NULL){ return 1; @@ -231,16 +241,12 @@ static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec_ json_get_int(j_acl, "priority", &acl->priority, true, 0); json_get_bool(j_acl, "allow", &acl->allow, true, false); - jtmp = cJSON_GetObjectItem(j_acl, "allow"); - if(jtmp && cJSON_IsBool(jtmp)){ - acl->allow = cJSON_IsTrue(jtmp); - } - - jtmp = cJSON_GetObjectItem(j_acl, "topic"); - if(jtmp && cJSON_IsString(jtmp)){ - acl->topic = mosquitto_strdup(jtmp->valuestring); + bool allow; + if(json_get_bool(j_acl, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS){ + acl->allow = allow; } + acl->topic = mosquitto_strdup(topic); if(acl->topic == NULL){ mosquitto_free(acl); continue; @@ -255,7 +261,7 @@ static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec_ int dynsec_roles__config_load(cJSON *tree) { - cJSON *j_roles, *j_role, *jtmp, *j_acls; + cJSON *j_roles, *j_role, *j_acls; struct dynsec__role *role; j_roles = cJSON_GetObjectItem(tree, "roles"); @@ -269,27 +275,31 @@ int dynsec_roles__config_load(cJSON *tree) cJSON_ArrayForEach(j_role, j_roles){ if(cJSON_IsObject(j_role) == true){ + /* Role name */ + char *rolename; + if(json_get_string(j_role, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ + continue; + } + role = dynsec_roles__find(rolename); + if(role){ + continue; + } + role = mosquitto_calloc(1, sizeof(struct dynsec__role)); if(role == NULL){ return MOSQ_ERR_NOMEM; } - /* Role name */ - jtmp = cJSON_GetObjectItem(j_role, "rolename"); - if(jtmp == NULL){ - mosquitto_free(role); - continue; - } - role->rolename = mosquitto_strdup(jtmp->valuestring); + role->rolename = mosquitto_strdup(rolename); if(role->rolename == NULL){ mosquitto_free(role); continue; } /* Text name */ - jtmp = cJSON_GetObjectItem(j_role, "textname"); - if(jtmp != NULL){ - role->text_name = mosquitto_strdup(jtmp->valuestring); + char *textname; + if(json_get_string(j_role, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ + role->text_name = mosquitto_strdup(textname); if(role->text_name == NULL){ mosquitto_free(role->rolename); mosquitto_free(role); @@ -298,9 +308,9 @@ int dynsec_roles__config_load(cJSON *tree) } /* Text description */ - jtmp = cJSON_GetObjectItem(j_role, "textdescription"); - if(jtmp != NULL){ - role->text_description = mosquitto_strdup(jtmp->valuestring); + char *textdescription; + if(json_get_string(j_role, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ + role->text_description = mosquitto_strdup(textdescription); if(role->text_description == NULL){ mosquitto_free(role->text_name); mosquitto_free(role->rolename); @@ -594,9 +604,9 @@ int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, char *rolename; char *topic; struct dynsec__role *role; - cJSON *jtmp, *j_acltype; struct dynsec__acl **acllist, *acl; int rc; + char *acltype; const char *admin_clientid, *admin_username; if(json_get_string(command, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ @@ -614,44 +624,37 @@ int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, return MOSQ_ERR_SUCCESS; } - j_acltype = cJSON_GetObjectItem(command, "acltype"); - if(j_acltype == NULL || !cJSON_IsString(j_acltype)){ + if(json_get_string(command, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid/missing acltype", correlation_data); return MOSQ_ERR_SUCCESS; } - if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_SEND)){ + if(!strcasecmp(acltype, ACL_TYPE_PUB_C_SEND)){ acllist = &role->acls.publish_c_send; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_RECV)){ + }else if(!strcasecmp(acltype, ACL_TYPE_PUB_C_RECV)){ acllist = &role->acls.publish_c_recv; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_LITERAL)){ + }else if(!strcasecmp(acltype, ACL_TYPE_SUB_LITERAL)){ acllist = &role->acls.subscribe_literal; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_PATTERN)){ + }else if(!strcasecmp(acltype, ACL_TYPE_SUB_PATTERN)){ acllist = &role->acls.subscribe_pattern; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_LITERAL)){ + }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_LITERAL)){ acllist = &role->acls.unsubscribe_literal; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_PATTERN)){ + }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_PATTERN)){ acllist = &role->acls.unsubscribe_pattern; }else{ dynsec__command_reply(j_responses, context, "addRoleACL", "Unknown acltype", correlation_data); return MOSQ_ERR_SUCCESS; } - jtmp = cJSON_GetObjectItem(command, "topic"); - if(jtmp && cJSON_IsString(jtmp)){ - if(mosquitto_validate_utf8(jtmp->valuestring, (int)strlen(jtmp->valuestring)) != MOSQ_ERR_SUCCESS){ + if(json_get_string(command, "topic", &topic, false) == MOSQ_ERR_SUCCESS){ + if(mosquitto_validate_utf8(topic, (int)strlen(topic)) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addRoleACL", "Topic not valid UTF-8", correlation_data); return MOSQ_ERR_INVAL; } - rc = mosquitto_sub_topic_check(jtmp->valuestring); + rc = mosquitto_sub_topic_check(topic); if(rc != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid ACL topic", correlation_data); return MOSQ_ERR_INVAL; } - topic = mosquitto_strdup(jtmp->valuestring); - if(topic == NULL){ - dynsec__command_reply(j_responses, context, "addRoleACL", "Internal error", correlation_data); - return MOSQ_ERR_SUCCESS; - } }else{ dynsec__command_reply(j_responses, context, "addRoleACL", "Invalid/missing topic", correlation_data); return MOSQ_ERR_SUCCESS; @@ -659,18 +662,21 @@ int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, HASH_FIND(hh, *acllist, topic, strlen(topic), acl); if(acl){ - mosquitto_free(topic); dynsec__command_reply(j_responses, context, "addRoleACL", "ACL with this topic already exists", correlation_data); return MOSQ_ERR_SUCCESS; } acl = mosquitto_calloc(1, sizeof(struct dynsec__acl)); if(acl == NULL){ - mosquitto_free(topic); dynsec__command_reply(j_responses, context, "addRoleACL", "Internal error", correlation_data); return MOSQ_ERR_SUCCESS; } - acl->topic = topic; + acl->topic = mosquitto_strdup(topic); + if(acl->topic == NULL){ + mosquitto_free(acl); + dynsec__command_reply(j_responses, context, "addRoleACL", "Internal error", correlation_data); + return MOSQ_ERR_SUCCESS; + } json_get_int(command, "priority", &acl->priority, true, 0); json_get_bool(command, "allow", &acl->allow, true, false); @@ -684,7 +690,7 @@ int dynsec_roles__process_add_acl(cJSON *j_responses, struct mosquitto *context, admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | addRoleACL | rolename=%s | acltype=%s | topic=%s | priority=%d | allow=%s", - admin_clientid, admin_username, rolename, j_acltype->valuestring, topic, acl->priority, acl->allow?"true":"false"); + admin_clientid, admin_username, rolename, acltype, topic, acl->priority, acl->allow?"true":"false"); return MOSQ_ERR_SUCCESS; } @@ -696,7 +702,7 @@ int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *conte struct dynsec__role *role; struct dynsec__acl **acllist, *acl; char *topic; - cJSON *j_acltype; + char *acltype; int rc; const char *admin_clientid, *admin_username; @@ -715,22 +721,21 @@ int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *conte return MOSQ_ERR_SUCCESS; } - j_acltype = cJSON_GetObjectItem(command, "acltype"); - if(j_acltype == NULL || !cJSON_IsString(j_acltype)){ + if(json_get_string(command, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){ dynsec__command_reply(j_responses, context, "removeRoleACL", "Invalid/missing acltype", correlation_data); return MOSQ_ERR_SUCCESS; } - if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_SEND)){ + if(!strcasecmp(acltype, ACL_TYPE_PUB_C_SEND)){ acllist = &role->acls.publish_c_send; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_PUB_C_RECV)){ + }else if(!strcasecmp(acltype, ACL_TYPE_PUB_C_RECV)){ acllist = &role->acls.publish_c_recv; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_LITERAL)){ + }else if(!strcasecmp(acltype, ACL_TYPE_SUB_LITERAL)){ acllist = &role->acls.subscribe_literal; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_SUB_PATTERN)){ + }else if(!strcasecmp(acltype, ACL_TYPE_SUB_PATTERN)){ acllist = &role->acls.subscribe_pattern; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_LITERAL)){ + }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_LITERAL)){ acllist = &role->acls.unsubscribe_literal; - }else if(!strcasecmp(j_acltype->valuestring, ACL_TYPE_UNSUB_PATTERN)){ + }else if(!strcasecmp(acltype, ACL_TYPE_UNSUB_PATTERN)){ acllist = &role->acls.unsubscribe_pattern; }else{ dynsec__command_reply(j_responses, context, "removeRoleACL", "Unknown acltype", correlation_data); @@ -762,7 +767,7 @@ int dynsec_roles__process_remove_acl(cJSON *j_responses, struct mosquitto *conte admin_clientid = mosquitto_client_id(context); admin_username = mosquitto_client_username(context); mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | removeRoleACL | rolename=%s | acltype=%s | topic=%s", - admin_clientid, admin_username, rolename, j_acltype->valuestring, topic); + admin_clientid, admin_username, rolename, acltype, topic); }else{ dynsec__command_reply(j_responses, context, "removeRoleACL", "ACL not found", correlation_data); diff --git a/set-version.sh b/set-version.sh index 81d01473..57c3bcb8 100755 --- a/set-version.sh +++ b/set-version.sh @@ -2,7 +2,7 @@ MAJOR=2 MINOR=0 -REVISION=15 +REVISION=16 sed -i "s/^VERSION=.*/VERSION=${MAJOR}.${MINOR}.${REVISION}/" config.mk diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a330a4e5..177ee2ec 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: mosquitto -version: 2.0.15 +version: 2.0.16 summary: Eclipse Mosquitto MQTT broker description: This is a message broker that supports version 5.0, 3.1.1, and 3.1 of the MQTT protocol. diff --git a/src/bridge.c b/src/bridge.c index f6e77d32..70c7ffe5 100644 --- a/src/bridge.c +++ b/src/bridge.c @@ -126,6 +126,9 @@ int bridge__new(struct mosquitto__bridge *bridge) } new_context->retain_available = bridge->outgoing_retain; new_context->protocol = bridge->protocol_version; + if(!bridge->clean_start_local){ + new_context->session_expiry_interval = UINT32_MAX; + } bridges = mosquitto__realloc(db.bridges, (size_t)(db.bridge_count+1)*sizeof(struct mosquitto *)); if(bridges){ diff --git a/src/conf.c b/src/conf.c index a3fbc7c4..80c0cd82 100644 --- a/src/conf.c +++ b/src/conf.c @@ -187,7 +187,7 @@ static void config__init_reload(struct mosquitto__config *config) config->log_timestamp = true; mosquitto__free(config->log_timestamp_format); config->log_timestamp_format = NULL; - config->max_keepalive = 65535; + config->max_keepalive = 0; config->max_packet_size = 0; config->max_inflight_messages = 20; config->max_queued_messages = 1000; @@ -1533,15 +1533,16 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload, }else if(!strcmp(token, "dlt")){ cr->log_dest |= MQTT3_LOG_DLT; }else if(!strcmp(token, "file")){ - cr->log_dest |= MQTT3_LOG_FILE; if(config->log_fptr || config->log_file){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate \"log_dest file\" value."); return MOSQ_ERR_INVAL; } /* Get remaining string. */ - token = &token[strlen(token)+1]; - while(token[0] == ' ' || token[0] == '\t'){ - token++; + token = saveptr; + if(token && token[0]){ + while(token[0] == ' ' || token[0] == '\t'){ + token++; + } } if(token[0]){ config->log_file = mosquitto__strdup(token); @@ -1553,6 +1554,7 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload, log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty \"log_dest file\" value in configuration."); return MOSQ_ERR_INVAL; } + cr->log_dest |= MQTT3_LOG_FILE; }else{ log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid log_dest value (%s).", token); return MOSQ_ERR_INVAL; diff --git a/src/context.c b/src/context.c index b2f851ea..3ed49765 100644 --- a/src/context.c +++ b/src/context.c @@ -83,9 +83,9 @@ struct mosquitto *context__init(mosq_sock_t sock) } } context->bridge = NULL; - context->msgs_in.inflight_maximum = db.config->max_inflight_messages; + context->msgs_in.inflight_maximum = 1; context->msgs_out.inflight_maximum = db.config->max_inflight_messages; - context->msgs_in.inflight_quota = db.config->max_inflight_messages; + context->msgs_in.inflight_quota = 1; context->msgs_out.inflight_quota = db.config->max_inflight_messages; context->max_qos = 2; #ifdef WITH_TLS @@ -98,6 +98,27 @@ struct mosquitto *context__init(mosq_sock_t sock) return context; } +static void context__cleanup_out_packets(struct mosquitto *context) +{ + struct mosquitto__packet *packet; + + if(!context) return; + + if(context->current_out_packet){ + packet__cleanup(context->current_out_packet); + mosquitto__free(context->current_out_packet); + context->current_out_packet = NULL; + } + while(context->out_packet){ + packet__cleanup(context->out_packet); + packet = context->out_packet; + context->out_packet = context->out_packet->next; + mosquitto__free(packet); + } + context->out_packet_count = 0; +} + + /* * This will result in any outgoing packets going unsent. If we're disconnected * forcefully then it is usually an error condition and shouldn't be a problem, @@ -106,8 +127,6 @@ struct mosquitto *context__init(mosq_sock_t sock) */ void context__cleanup(struct mosquitto *context, bool force_free) { - struct mosquitto__packet *packet; - if(!context) return; if(force_free){ @@ -121,6 +140,7 @@ void context__cleanup(struct mosquitto *context, bool force_free) #endif alias__free_all(context); + context__cleanup_out_packets(context); mosquitto__free(context->auth_method); context->auth_method = NULL; @@ -148,18 +168,7 @@ void context__cleanup(struct mosquitto *context, bool force_free) context->id = NULL; } packet__cleanup(&(context->in_packet)); - if(context->current_out_packet){ - packet__cleanup(context->current_out_packet); - mosquitto__free(context->current_out_packet); - context->current_out_packet = NULL; - } - while(context->out_packet){ - packet__cleanup(context->out_packet); - packet = context->out_packet; - context->out_packet = context->out_packet->next; - mosquitto__free(packet); - } - context->out_packet_count = 0; + context__cleanup_out_packets(context); #if defined(WITH_BROKER) && defined(__GLIBC__) && defined(WITH_ADNS) if(context->adns){ gai_cancel(context->adns); @@ -214,19 +223,20 @@ void context__disconnect(struct mosquitto *context) context__send_will(context); net__socket_close(context); - if(context->session_expiry_interval == 0){ - /* Client session is due to be expired now */ #ifdef WITH_BRIDGE - if(context->bridge == NULL) + if(context->bridge == NULL) + /* Outgoing bridge connection never expire */ #endif - { + { + if(context->session_expiry_interval == 0){ + /* Client session is due to be expired now */ if(context->will_delay_interval == 0){ /* This will be done later, after the will is published for delay>0. */ context__add_to_disused(context); } + }else{ + session_expiry__add(context); } - }else{ - session_expiry__add(context); } keepalive__remove(context); mosquitto__set_state(context, mosq_cs_disconnected); diff --git a/src/database.c b/src/database.c index 5e77a281..1a926f50 100644 --- a/src/database.c +++ b/src/database.c @@ -555,7 +555,7 @@ int db__message_insert(struct mosquitto *context, uint16_t mid, enum mosquitto_m } #endif - msg = mosquitto__malloc(sizeof(struct mosquitto_client_msg)); + msg = mosquitto__calloc(1, sizeof(struct mosquitto_client_msg)); if(!msg) return MOSQ_ERR_NOMEM; msg->prev = NULL; msg->next = NULL; @@ -613,6 +613,8 @@ int db__message_insert(struct mosquitto *context, uint16_t mid, enum mosquitto_m if(dir == mosq_md_out && msg->qos > 0 && state != mosq_ms_queued){ util__decrement_send_quota(context); + }else if(dir == mosq_md_in && msg->qos > 0 && state != mosq_ms_queued){ + util__decrement_receive_quota(context); } if(dir == mosq_md_out && update){ @@ -796,23 +798,24 @@ int db__message_store(const struct mosquitto *source, struct mosquitto_msg_store return MOSQ_ERR_SUCCESS; } -int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored) +int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg) { - struct mosquitto_client_msg *tail; + struct mosquitto_client_msg *cmsg; + + *client_msg = NULL; if(!context) return MOSQ_ERR_INVAL; - *stored = NULL; - DL_FOREACH(context->msgs_in.inflight, tail){ - if(tail->store->source_mid == mid){ - *stored = tail->store; + DL_FOREACH(context->msgs_in.inflight, cmsg){ + if(cmsg->store->source_mid == mid){ + *client_msg = cmsg; return MOSQ_ERR_SUCCESS; } } - DL_FOREACH(context->msgs_in.queued, tail){ - if(tail->store->source_mid == mid){ - *stored = tail->store; + DL_FOREACH(context->msgs_in.queued, cmsg){ + if(cmsg->store->source_mid == mid){ + *client_msg = cmsg; return MOSQ_ERR_SUCCESS; } } @@ -914,6 +917,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context) }else{ /* Message state can be preserved here because it should match * whatever the client has got. */ + msg->dup = 0; } } @@ -924,6 +928,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context) * will be sent out of order. */ DL_FOREACH_SAFE(context->msgs_in.queued, msg, tmp){ + msg->dup = 0; db__msg_add_to_queued_stats(&context->msgs_in, msg); if(db__ready_for_flight(context, mosq_md_in, msg->qos)){ switch(msg->qos){ diff --git a/src/handle_connect.c b/src/handle_connect.c index 21405adf..cb22e358 100644 --- a/src/handle_connect.c +++ b/src/handle_connect.c @@ -375,6 +375,10 @@ static int will__read(struct mosquitto *context, const char *client_id, struct m will_struct->msg.topic = will_topic_mount; } + if(!strncmp(will_struct->msg.topic, "$CONTROL/", strlen("$CONTROL/"))){ + rc = MOSQ_ERR_ACL_DENIED; + goto error_cleanup; + } rc = mosquitto_pub_topic_check(will_struct->msg.topic); if(rc) goto error_cleanup; @@ -790,11 +794,22 @@ int handle__connect(struct mosquitto *context) rc = MOSQ_ERR_AUTH; goto handle_connect_error; } + const char *new_username; #if OPENSSL_VERSION_NUMBER < 0x10100000L - context->username = mosquitto__strdup((char *) ASN1_STRING_data(name_asn1)); + new_username = (const char *) ASN1_STRING_data(name_asn1); #else - context->username = mosquitto__strdup((char *) ASN1_STRING_get0_data(name_asn1)); + new_username = (const char *) ASN1_STRING_get0_data(name_asn1); #endif + if(mosquitto_validate_utf8(new_username, (int)strlen(new_username))){ + if(context->protocol == mosq_p_mqtt5){ + send__connack(context, 0, MQTT_RC_BAD_USERNAME_OR_PASSWORD, NULL); + }else{ + send__connack(context, 0, CONNACK_REFUSED_BAD_USERNAME_PASSWORD, NULL); + } + X509_free(client_cert); + return MOSQ_ERR_AUTH; + } + context->username = mosquitto__strdup(new_username); if(!context->username){ if(context->protocol == mosq_p_mqtt5){ send__connack(context, 0, MQTT_RC_SERVER_UNAVAILABLE, NULL); @@ -936,6 +951,7 @@ int handle__connect(struct mosquitto *context) handle_connect_error: + mosquitto_property_free_all(&properties); mosquitto__free(auth_data); mosquitto__free(client_id); mosquitto__free(username); @@ -946,7 +962,13 @@ handle_connect_error: mosquitto__free(will_struct->msg.topic); mosquitto__free(will_struct); } - context->will = NULL; + if(context->will){ + mosquitto_property_free_all(&context->will->properties); + mosquitto__free(context->will->msg.payload); + mosquitto__free(context->will->msg.topic); + mosquitto__free(context->will); + context->will = NULL; + } #ifdef WITH_TLS if(client_cert) X509_free(client_cert); #endif diff --git a/src/handle_publish.c b/src/handle_publish.c index 111f0316..d3aa5ce2 100644 --- a/src/handle_publish.c +++ b/src/handle_publish.c @@ -42,6 +42,7 @@ int handle__publish(struct mosquitto *context) uint8_t header = context->in_packet.command; int res = 0; struct mosquitto_msg_store *msg, *stored = NULL; + struct mosquitto_client_msg *cmsg_stored = NULL; size_t len; uint16_t slen; char *topic_mount; @@ -287,24 +288,24 @@ int handle__publish(struct mosquitto *context) } if(msg->qos > 0){ - db__message_store_find(context, msg->source_mid, &stored); + db__message_store_find(context, msg->source_mid, &cmsg_stored); } - if(stored && msg->source_mid != 0 && - (stored->qos != msg->qos - || stored->payloadlen != msg->payloadlen - || strcmp(stored->topic, msg->topic) - || memcmp(stored->payload, msg->payload, msg->payloadlen) )){ + if(cmsg_stored && cmsg_stored->store && msg->source_mid != 0 && + (cmsg_stored->store->qos != msg->qos + || cmsg_stored->store->payloadlen != msg->payloadlen + || strcmp(cmsg_stored->store->topic, msg->topic) + || memcmp(cmsg_stored->store->payload, msg->payload, msg->payloadlen) )){ log__printf(NULL, MOSQ_LOG_WARNING, "Reused message ID %u from %s detected. Clearing from storage.", msg->source_mid, context->id); db__message_remove_incoming(context, msg->source_mid); - stored = NULL; + cmsg_stored = NULL; } - if(!stored){ + if(!cmsg_stored){ if(msg->qos == 0 || db__ready_for_flight(context, mosq_md_in, msg->qos) - || db__ready_for_queue(context, msg->qos, &context->msgs_in)){ + ){ dup = 0; rc = db__message_store(context, msg, message_expiry_interval, 0, mosq_mo_client); @@ -316,10 +317,13 @@ int handle__publish(struct mosquitto *context) } stored = msg; msg = NULL; + dup = 0; }else{ db__msg_store_free(msg); msg = NULL; - dup = 1; + stored = cmsg_stored->store; + cmsg_stored->dup++; + dup = cmsg_stored->dup; } switch(stored->qos){ @@ -345,11 +349,17 @@ int handle__publish(struct mosquitto *context) }else{ res = 0; } + /* db__message_insert() returns 2 to indicate dropped message * due to queue. This isn't an error so don't disconnect them. */ /* FIXME - this is no longer necessary due to failing early above */ if(!res){ - if(send__pubrec(context, stored->source_mid, 0, NULL)) rc = 1; + if(dup == 0 || dup == 1){ + rc2 = send__pubrec(context, stored->source_mid, 0, NULL); + if(rc2) rc = rc2; + }else{ + return MOSQ_ERR_PROTOCOL; + } }else if(res == 1){ rc = 1; } @@ -374,6 +384,9 @@ process_bad_message: } db__msg_store_free(msg); } + if(context->out_packet_count >= db.config->max_queued_messages){ + rc = MQTT_RC_QUOTA_EXCEEDED; + } return rc; } diff --git a/src/logging.c b/src/logging.c index 6795d7e9..0af2787f 100644 --- a/src/logging.c +++ b/src/logging.c @@ -20,6 +20,7 @@ Contributors: #include #include #include +#include #ifndef WIN32 #include #endif @@ -129,11 +130,16 @@ int log__init(struct mosquitto__config *config) log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open log file %s for writing.", config->log_file); } } + if(log_destinations & MQTT3_LOG_STDOUT){ + setvbuf(stdout, NULL, _IOLBF, 0); + } #ifdef WITH_DLT - dlt_fifo_check(); - if(dlt_allowed){ - DLT_REGISTER_APP("MQTT","mosquitto log"); - dlt_register_context(&dltContext, "MQTT", "mosquitto DLT context"); + if(log_destinations & MQTT3_LOG_DLT){ + dlt_fifo_check(); + if(dlt_allowed){ + DLT_REGISTER_APP("MQTT","mosquitto log"); + dlt_register_context(&dltContext, "MQTT", "mosquitto DLT context"); + } } #endif return rc; @@ -291,7 +297,7 @@ static int log__vprintf(unsigned int priority, const char *fmt, va_list va) log_line_pos = (size_t)snprintf(log_line, sizeof(log_line), "Time error"); } }else{ - log_line_pos = (size_t)snprintf(log_line, sizeof(log_line), "%d", (int)db.now_real_s); + log_line_pos = (size_t)snprintf(log_line, sizeof(log_line), "%" PRIu64, (uint64_t)db.now_real_s); } if(log_line_pos < sizeof(log_line)-3){ log_line[log_line_pos] = ':'; diff --git a/src/mosquitto.c b/src/mosquitto.c index 09632f28..431254d8 100644 --- a/src/mosquitto.c +++ b/src/mosquitto.c @@ -160,9 +160,18 @@ static void mosquitto__daemonise(void) exit(1); } - assert(freopen("/dev/null", "r", stdin)); - assert(freopen("/dev/null", "w", stdout)); - assert(freopen("/dev/null", "w", stderr)); + if(!freopen("/dev/null", "r", stdin)){ + log__printf(NULL, MOSQ_LOG_ERR, "Error whilst daemonising (%s): %s", "stdin", strerror(errno)); + exit(1); + } + if(!freopen("/dev/null", "w", stdout)){ + log__printf(NULL, MOSQ_LOG_ERR, "Error whilst daemonising (%s): %s", "stdout", strerror(errno)); + exit(1); + } + if(!freopen("/dev/null", "w", stderr)){ + log__printf(NULL, MOSQ_LOG_ERR, "Error whilst daemonising (%s): %s", "stderr", strerror(errno)); + exit(1); + } #else log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Can't start in daemon mode in Windows."); #endif @@ -475,7 +484,12 @@ int main(int argc, char *argv[]) #endif #ifdef WIN32 - _setmaxstdio(2048); + if(_setmaxstdio(8192) != 8192){ + /* Old limit was 2048 */ + if(_setmaxstdio(2048) != 2048){ + log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Unable to increase maximum allowed connections. This session may be limited to 512 connections."); + } + } #endif memset(&db, 0, sizeof(struct mosquitto_db)); diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index c28eaa2a..2cfa9d2b 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -394,7 +394,7 @@ struct mosquitto_client_msg{ bool retain; enum mosquitto_msg_direction direction; enum mosquitto_msg_state state; - bool dup; + uint8_t dup; }; @@ -651,7 +651,7 @@ void db__message_dequeue_first(struct mosquitto *context, struct mosquitto_msg_d int db__messages_delete(struct mosquitto *context, bool force_free); int db__messages_easy_queue(struct mosquitto *context, const char *topic, uint8_t qos, uint32_t payloadlen, const void *payload, int retain, uint32_t message_expiry_interval, mosquitto_property **properties); int db__message_store(const struct mosquitto *source, struct mosquitto_msg_store *stored, uint32_t message_expiry_interval, dbid_t store_id, enum mosquitto_msg_origin origin); -int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored); +int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg); void db__msg_store_add(struct mosquitto_msg_store *store); void db__msg_store_remove(struct mosquitto_msg_store *store); void db__msg_store_ref_inc(struct mosquitto_msg_store *store); diff --git a/src/net.c b/src/net.c index 80d2c8d6..8fab9b3d 100644 --- a/src/net.c +++ b/src/net.c @@ -56,6 +56,7 @@ Contributors: #include "mosquitto_broker_internal.h" #include "mqtt_protocol.h" #include "memory_mosq.h" +#include "misc_mosq.h" #include "net_mosq.h" #include "util_mosq.h" @@ -295,6 +296,10 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, unsigned } if(listener->use_identity_as_username){ + if(mosquitto_validate_utf8(identity, (int)strlen(identity))){ + mosquitto__free(psk_key); + return 0; + } context->username = mosquitto__strdup(identity); if(!context->username){ mosquitto__free(psk_key); @@ -343,7 +348,7 @@ int net__tls_server_ctx(struct mosquitto__listener *listener) #endif if(listener->tls_version == NULL){ - SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); + SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); #ifdef SSL_OP_NO_TLSv1_3 }else if(!strcmp(listener->tls_version, "tlsv1.3")){ SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2); @@ -416,7 +421,7 @@ int net__tls_server_ctx(struct mosquitto__listener *listener) #endif if(listener->dhparamfile){ - dhparamfile = fopen(listener->dhparamfile, "r"); + dhparamfile = mosquitto__fopen(listener->dhparamfile, "r", true); if(!dhparamfile){ log__printf(NULL, MOSQ_LOG_ERR, "Error loading dhparamfile \"%s\".", listener->dhparamfile); return MOSQ_ERR_TLS; @@ -479,7 +484,7 @@ int net__load_certificates(struct mosquitto__listener *listener) net__print_ssl_error(NULL); return MOSQ_ERR_TLS; } - if(listener->tls_engine == NULL){ + if(listener->tls_engine == NULL || listener->tls_keyform == mosq_k_pem){ rc = SSL_CTX_use_PrivateKey_file(listener->ssl_ctx, listener->keyfile, SSL_FILETYPE_PEM); if(rc != 1){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server key file \"%s\". Check keyfile.", listener->keyfile); @@ -622,6 +627,8 @@ static int net__bind_interface(struct mosquitto__listener *listener, struct addr * matching interface in the later bind(). */ struct ifaddrs *ifaddr, *ifa; + bool have_interface = false; + if(getifaddrs(&ifaddr) < 0){ net__print_error(MOSQ_LOG_ERR, "Error: %s"); return MOSQ_ERR_ERRNO; @@ -632,49 +639,56 @@ static int net__bind_interface(struct mosquitto__listener *listener, struct addr continue; } - if(!strcasecmp(listener->bind_interface, ifa->ifa_name) - && ifa->ifa_addr->sa_family == rp->ai_addr->sa_family){ + if(!strcasecmp(listener->bind_interface, ifa->ifa_name)){ + have_interface = true; - if(rp->ai_addr->sa_family == AF_INET){ - if(listener->host && - memcmp(&((struct sockaddr_in *)rp->ai_addr)->sin_addr, - &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, - sizeof(struct in_addr))){ + if(ifa->ifa_addr->sa_family == rp->ai_addr->sa_family){ + if(rp->ai_addr->sa_family == AF_INET){ + if(listener->host && + memcmp(&((struct sockaddr_in *)rp->ai_addr)->sin_addr, + &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, + sizeof(struct in_addr))){ - log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface address for %s does not match specified listener address (%s).", - listener->bind_interface, listener->host); - return MOSQ_ERR_INVAL; - }else{ - memcpy(&((struct sockaddr_in *)rp->ai_addr)->sin_addr, - &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, - sizeof(struct in_addr)); + log__printf(NULL, MOSQ_LOG_ERR, "Error: Interface address for %s does not match specified listener address (%s).", + listener->bind_interface, listener->host); + return MOSQ_ERR_INVAL; + }else{ + memcpy(&((struct sockaddr_in *)rp->ai_addr)->sin_addr, + &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, + sizeof(struct in_addr)); - freeifaddrs(ifaddr); - return MOSQ_ERR_SUCCESS; - } - }else if(rp->ai_addr->sa_family == AF_INET6){ - if(listener->host && - memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, - &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, - sizeof(struct in6_addr))){ + freeifaddrs(ifaddr); + return MOSQ_ERR_SUCCESS; + } + }else if(rp->ai_addr->sa_family == AF_INET6){ + if(listener->host && + memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, + &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, + sizeof(struct in6_addr))){ - log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface address for %s does not match specified listener address (%s).", - listener->bind_interface, listener->host); - return MOSQ_ERR_INVAL; - }else{ - memcpy(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, - &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, - sizeof(struct in6_addr)); - freeifaddrs(ifaddr); - return MOSQ_ERR_SUCCESS; + log__printf(NULL, MOSQ_LOG_ERR, "Error: Interface address for %s does not match specified listener address (%s).", + listener->bind_interface, listener->host); + return MOSQ_ERR_INVAL; + }else{ + memcpy(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, + &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, + sizeof(struct in6_addr)); + freeifaddrs(ifaddr); + return MOSQ_ERR_SUCCESS; + } } } } } freeifaddrs(ifaddr); - log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface %s does not support %s configuration.", - listener->bind_interface, rp->ai_addr->sa_family == AF_INET ? "IPv4" : "IPv6"); - return MOSQ_ERR_NOT_FOUND; + if(have_interface){ + log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface %s does not support %s configuration.", + listener->bind_interface, rp->ai_addr->sa_family == AF_INET ? "IPv4" : "IPv6"); + return MOSQ_ERR_NOT_SUPPORTED; + }else{ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Interface %s does not exist.", listener->bind_interface); + return MOSQ_ERR_NOT_FOUND; + } } #endif @@ -756,10 +770,16 @@ static int net__socket_listen_tcp(struct mosquitto__listener *listener) if(listener->bind_interface){ /* It might be possible that an interface does not support all relevant sa_families. * We should successfully find at least one. */ - if(net__bind_interface(listener, rp)){ + rc = net__bind_interface(listener, rp); + if(rc){ COMPAT_CLOSE(sock); listener->sock_count--; - continue; + if(rc == MOSQ_ERR_NOT_FOUND || rc == MOSQ_ERR_INVAL){ + freeaddrinfo(ainfo); + return rc; + }else{ + continue; + } } interface_bound = true; } diff --git a/src/password_mosq.c b/src/password_mosq.c index b8836f45..b11ef09c 100644 --- a/src/password_mosq.c +++ b/src/password_mosq.c @@ -201,9 +201,7 @@ int pw__memcmp_const(const void *a, const void *b, size_t len) if(!a || !b) return 1; for(i=0; ipersistence_filepath, "rb", false); + fptr = mosquitto__fopen(db.config->persistence_filepath, "rb", true); if(fptr == NULL) return MOSQ_ERR_SUCCESS; rlen = fread(&header, 1, 15, fptr); if(rlen == 0){ diff --git a/src/persist_read_v234.c b/src/persist_read_v234.c index 7460c309..49147bba 100644 --- a/src/persist_read_v234.c +++ b/src/persist_read_v234.c @@ -180,9 +180,9 @@ int persist__chunk_msg_store_read_v234(FILE *db_fptr, struct P_msg_store *chunk, log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); return MOSQ_ERR_NOMEM; } + read_e(db_fptr, chunk->payload, chunk->F.payloadlen); /* Ensure zero terminated regardless of contents */ ((uint8_t *)chunk->payload)[chunk->F.payloadlen] = 0; - read_e(db_fptr, chunk->payload, chunk->F.payloadlen); } return MOSQ_ERR_SUCCESS; diff --git a/src/persist_read_v5.c b/src/persist_read_v5.c index abc9a580..173046ed 100644 --- a/src/persist_read_v5.c +++ b/src/persist_read_v5.c @@ -203,9 +203,9 @@ int persist__chunk_msg_store_read_v56(FILE *db_fptr, struct P_msg_store *chunk, log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); return MOSQ_ERR_NOMEM; } + read_e(db_fptr, chunk->payload, chunk->F.payloadlen); /* Ensure zero terminated regardless of contents */ ((uint8_t *)chunk->payload)[chunk->F.payloadlen] = 0; - read_e(db_fptr, chunk->payload, chunk->F.payloadlen); } if(length > 0){ diff --git a/src/persist_write.c b/src/persist_write.c index fb3632fe..954e36a4 100644 --- a/src/persist_write.c +++ b/src/persist_write.c @@ -47,8 +47,6 @@ static int persist__client_messages_save(FILE *db_fptr, struct mosquitto *contex assert(db_fptr); assert(context); - memset(&chunk, 0, sizeof(struct P_client_msg)); - cmsg = queue; while(cmsg){ if(!strncmp(cmsg->store->topic, "$SYS", 4) @@ -61,6 +59,8 @@ static int persist__client_messages_save(FILE *db_fptr, struct mosquitto *contex continue; } + memset(&chunk, 0, sizeof(struct P_client_msg)); + chunk.F.store_id = cmsg->store->db_id; chunk.F.mid = cmsg->mid; chunk.F.id_len = (uint16_t)strlen(context->id); @@ -91,8 +91,6 @@ static int persist__message_store_save(FILE *db_fptr) assert(db_fptr); - memset(&chunk, 0, sizeof(struct P_msg_store)); - stored = db.msg_store; while(stored){ if(stored->ref_count < 1 || stored->topic == NULL){ @@ -100,6 +98,8 @@ static int persist__message_store_save(FILE *db_fptr) continue; } + memset(&chunk, 0, sizeof(struct P_msg_store)); + if(!strncmp(stored->topic, "$SYS", 4)){ if(stored->ref_count <= 1 && stored->dest_id_count == 0){ /* $SYS messages that are only retained shouldn't be persisted. */ @@ -164,14 +164,17 @@ static int persist__client_save(FILE *db_fptr) assert(db_fptr); - memset(&chunk, 0, sizeof(struct P_client)); - HASH_ITER(hh_id, db.contexts_by_id, context, ctxt_tmp){ - if(context && (context->clean_start == false + memset(&chunk, 0, sizeof(struct P_client)); + + if(context && #ifdef WITH_BRIDGE - || (context->bridge && context->bridge->clean_start_local == false) + ((!context->bridge && context->clean_start == false) + || (context->bridge && context->bridge->clean_start_local == false)) +#else + context->clean_start == false #endif - )){ + ){ chunk.F.session_expiry_time = context->session_expiry_time; if(context->session_expiry_interval != 0 && context->session_expiry_interval != UINT32_MAX && context->session_expiry_time == 0){ chunk.F.session_expiry_time = context->session_expiry_interval + db.now_real_s; @@ -221,8 +224,6 @@ static int persist__subs_save(FILE *db_fptr, struct mosquitto__subhier *node, co size_t slen; int rc; - memset(&sub_chunk, 0, sizeof(struct P_sub)); - slen = strlen(topic) + node->topic_len + 2; thistopic = mosquitto__malloc(sizeof(char)*slen); if(!thistopic) return MOSQ_ERR_NOMEM; @@ -235,6 +236,8 @@ static int persist__subs_save(FILE *db_fptr, struct mosquitto__subhier *node, co sub = node->subs; while(sub){ if(sub->context->clean_start == false && sub->context->id){ + memset(&sub_chunk, 0, sizeof(struct P_sub)); + sub_chunk.F.identifier = sub->identifier; sub_chunk.F.id_len = (uint16_t)strlen(sub->context->id); sub_chunk.F.topic_len = (uint16_t)strlen(thistopic); @@ -278,10 +281,10 @@ static int persist__retain_save(FILE *db_fptr, struct mosquitto__retainhier *nod struct P_retain retain_chunk; int rc; - memset(&retain_chunk, 0, sizeof(struct P_retain)); - if(node->retained && strncmp(node->retained->topic, "$SYS", 4)){ /* Don't save $SYS messages. */ + memset(&retain_chunk, 0, sizeof(struct P_retain)); + retain_chunk.F.store_id = node->retained->db_id; rc = persist__chunk_retain_write_v6(db_fptr, &retain_chunk); if(rc){ diff --git a/src/plugin_public.c b/src/plugin_public.c index a800090c..cc1c0ded 100644 --- a/src/plugin_public.c +++ b/src/plugin_public.c @@ -244,6 +244,9 @@ int mosquitto_set_username(struct mosquitto *client, const char *username) if(!client) return MOSQ_ERR_INVAL; if(username){ + if(mosquitto_validate_utf8(username, (int)strlen(username))){ + return MOSQ_ERR_MALFORMED_UTF8; + } u_dup = mosquitto__strdup(username); if(!u_dup) return MOSQ_ERR_NOMEM; }else{ diff --git a/src/property_broker.c b/src/property_broker.c index e829b300..a0c856a9 100644 --- a/src/property_broker.c +++ b/src/property_broker.c @@ -103,6 +103,7 @@ int property__process_will(struct mosquitto *context, struct mosquitto_message_a break; default: + msg->properties = msg_properties; return MOSQ_ERR_PROTOCOL; break; } diff --git a/src/retain.c b/src/retain.c index e5065daf..6c303c96 100644 --- a/src/retain.c +++ b/src/retain.c @@ -72,6 +72,25 @@ int retain__init(void) } +void retain__clean_empty_hierarchy(struct mosquitto__retainhier *retainhier) +{ + struct mosquitto__retainhier *parent; + + while(retainhier){ + if(retainhier->children || retainhier->retained || retainhier->parent == NULL){ + /* Entry is being used */ + return; + }else{ + HASH_DELETE(hh, retainhier->parent->children, retainhier); + mosquitto__free(retainhier->topic); + parent = retainhier->parent; + mosquitto__free(retainhier); + retainhier = parent; + } + } +} + + int retain__store(const char *topic, struct mosquitto_msg_store *stored, char **split_topics) { struct mosquitto__retainhier *retainhier; @@ -124,6 +143,7 @@ int retain__store(const char *topic, struct mosquitto_msg_store *stored, char ** #endif }else{ retainhier->retained = NULL; + retain__clean_empty_hierarchy(retainhier); } return MOSQ_ERR_SUCCESS; diff --git a/src/security_default.c b/src/security_default.c index 4fc6a78b..f659ce4f 100644 --- a/src/security_default.c +++ b/src/security_default.c @@ -530,7 +530,7 @@ static int aclfile__parse(struct mosquitto__security_options *security_opts) return MOSQ_ERR_NOMEM; } - aclfptr = mosquitto__fopen(security_opts->acl_file, "rt", false); + aclfptr = mosquitto__fopen(security_opts->acl_file, "rt", true); if(!aclfptr){ mosquitto__free(buf); log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open acl_file \"%s\".", security_opts->acl_file); @@ -755,7 +755,7 @@ static int pwfile__parse(const char *file, struct mosquitto__unpwd **root) return MOSQ_ERR_NOMEM; } - pwfile = mosquitto__fopen(file, "rt", false); + pwfile = mosquitto__fopen(file, "rt", true); if(!pwfile){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open pwfile \"%s\".", file); mosquitto__free(buf); @@ -980,9 +980,7 @@ static int mosquitto__memcmp_const(const void *a, const void *b, size_t len) if(!a || !b) return 1; for(i=0; isession_expiry_time = db.now_real_s; + + if(db.config->persistent_client_expiration == 0){ + /* No global expiry, so use the client expiration interval */ + context->session_expiry_time += context->session_expiry_interval; + }else{ + /* We have a global expiry interval */ + if(db.config->persistent_client_expiration < context->session_expiry_interval){ + /* The client expiry is longer than the global expiry, so use the global */ + context->session_expiry_time += db.config->persistent_client_expiration; + }else{ + /* The global expiry is longer than the client expiry, so use the client */ + context->session_expiry_time += context->session_expiry_interval; + } + } +} + + int session_expiry__add(struct mosquitto *context) { struct session_expiry_list *item; @@ -59,21 +79,7 @@ int session_expiry__add(struct mosquitto *context) if(!item) return MOSQ_ERR_NOMEM; item->context = context; - item->context->session_expiry_time = db.now_real_s; - - if(db.config->persistent_client_expiration == 0){ - /* No global expiry, so use the client expiration interval */ - item->context->session_expiry_time += item->context->session_expiry_interval; - }else{ - /* We have a global expiry interval */ - if(db.config->persistent_client_expiration < item->context->session_expiry_interval){ - /* The client expiry is longer than the global expiry, so use the global */ - item->context->session_expiry_time += db.config->persistent_client_expiration; - }else{ - /* The global expiry is longer than the client expiry, so use the client */ - item->context->session_expiry_time += item->context->session_expiry_interval; - } - } + set_session_expiry_time(item->context); context->expiry_list_item = item; DL_INSERT_INORDER(expiry_list, item, session_expiry__cmp); @@ -98,7 +104,12 @@ int session_expiry__add_from_persistence(struct mosquitto *context, time_t expir if(!item) return MOSQ_ERR_NOMEM; item->context = context; - item->context->session_expiry_time = expiry_time; + if(expiry_time){ + item->context->session_expiry_time = expiry_time; + }else{ + set_session_expiry_time(item->context); + + } context->expiry_list_item = item; DL_INSERT_INORDER(expiry_list, item, session_expiry__cmp); @@ -165,4 +176,3 @@ void session_expiry__check(void) } } } - diff --git a/src/subs.c b/src/subs.c index 687537d9..c5c6b5a3 100644 --- a/src/subs.c +++ b/src/subs.c @@ -389,6 +389,7 @@ static int sub__remove_normal(struct mosquitto *context, struct mosquitto__subhi if(context->subs[i] && context->subs[i]->hier == subhier){ mosquitto__free(context->subs[i]); context->subs[i] = NULL; + context->sub_count--; break; } } @@ -429,6 +430,7 @@ static int sub__remove_shared(struct mosquitto *context, struct mosquitto__subhi mosquitto__free(context->subs[i]); context->subs[i] = NULL; + context->sub_count--; break; } } diff --git a/src/sys_tree.c b/src/sys_tree.c index fd339214..c44565a4 100644 --- a/src/sys_tree.c +++ b/src/sys_tree.c @@ -23,6 +23,7 @@ Contributors: #include #include #include +#include #include "mosquitto_broker_internal.h" #include "memory_mosq.h" @@ -76,31 +77,31 @@ static void sys_tree__update_clients(char *buf) if(client_count != count_total){ client_count = count_total; len = (uint32_t)snprintf(buf, BUFLEN, "%d", client_count); - db__messages_easy_queue(NULL, "$SYS/broker/clients/total", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/total", SYS_TREE_QOS, len, buf, 1, 0, NULL); if(client_count > client_max){ client_max = client_count; len = (uint32_t)snprintf(buf, BUFLEN, "%d", client_max); - db__messages_easy_queue(NULL, "$SYS/broker/clients/maximum", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/maximum", SYS_TREE_QOS, len, buf, 1, 0, NULL); } } if(disconnected_count != count_total-count_by_sock){ disconnected_count = count_total-count_by_sock; len = (uint32_t)snprintf(buf, BUFLEN, "%d", disconnected_count); - db__messages_easy_queue(NULL, "$SYS/broker/clients/inactive", SYS_TREE_QOS, len, buf, 1, 60, NULL); - db__messages_easy_queue(NULL, "$SYS/broker/clients/disconnected", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/inactive", SYS_TREE_QOS, len, buf, 1, 0, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/disconnected", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(connected_count != count_by_sock){ connected_count = count_by_sock; len = (uint32_t)snprintf(buf, BUFLEN, "%d", connected_count); - db__messages_easy_queue(NULL, "$SYS/broker/clients/active", SYS_TREE_QOS, len, buf, 1, 60, NULL); - db__messages_easy_queue(NULL, "$SYS/broker/clients/connected", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/active", SYS_TREE_QOS, len, buf, 1, 0, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/connected", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(g_clients_expired != clients_expired){ clients_expired = g_clients_expired; len = (uint32_t)snprintf(buf, BUFLEN, "%d", clients_expired); - db__messages_easy_queue(NULL, "$SYS/broker/clients/expired", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/clients/expired", SYS_TREE_QOS, len, buf, 1, 0, NULL); } } @@ -116,13 +117,13 @@ static void sys_tree__update_memory(char *buf) if(current_heap != value_ul){ current_heap = value_ul; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", current_heap); - db__messages_easy_queue(NULL, "$SYS/broker/heap/current", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/heap/current", SYS_TREE_QOS, len, buf, 1, 0, NULL); } value_ul =mosquitto__max_memory_used(); if(max_heap != value_ul){ max_heap = value_ul; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", max_heap); - db__messages_easy_queue(NULL, "$SYS/broker/heap/maximum", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/heap/maximum", SYS_TREE_QOS, len, buf, 1, 0, NULL); } } #endif @@ -135,12 +136,12 @@ static void calc_load(char *buf, const char *topic, bool initial, double exponen if (initial) { new_value = *current; len = (uint32_t)snprintf(buf, BUFLEN, "%.2f", new_value); - db__messages_easy_queue(NULL, topic, SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, topic, SYS_TREE_QOS, len, buf, 1, 0, NULL); } else { new_value = interval + exponent*((*current) - interval); if(fabs(new_value - (*current)) >= 0.01){ len = (uint32_t)snprintf(buf, BUFLEN, "%.2f", new_value); - db__messages_easy_queue(NULL, topic, SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, topic, SYS_TREE_QOS, len, buf, 1, 0, NULL); } } (*current) = new_value; @@ -217,8 +218,8 @@ void sys_tree__update(int interval, time_t start_time) if(interval && db.now_s - interval > last_update){ uptime = db.now_s - start_time; - len = (uint32_t)snprintf(buf, BUFLEN, "%d seconds", (int)uptime); - db__messages_easy_queue(NULL, "$SYS/broker/uptime", SYS_TREE_QOS, len, buf, 1, 60, NULL); + len = (uint32_t)snprintf(buf, BUFLEN, "%" PRIu64 " seconds", (uint64_t)uptime); + db__messages_easy_queue(NULL, "$SYS/broker/uptime", SYS_TREE_QOS, len, buf, 1, 0, NULL); sys_tree__update_clients(buf); initial_publish = false; @@ -287,32 +288,32 @@ void sys_tree__update(int interval, time_t start_time) if(db.msg_store_count != msg_store_count){ msg_store_count = db.msg_store_count; len = (uint32_t)snprintf(buf, BUFLEN, "%d", msg_store_count); - db__messages_easy_queue(NULL, "$SYS/broker/messages/stored", SYS_TREE_QOS, len, buf, 1, 60, NULL); - db__messages_easy_queue(NULL, "$SYS/broker/store/messages/count", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/messages/stored", SYS_TREE_QOS, len, buf, 1, 0, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/store/messages/count", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if (db.msg_store_bytes != msg_store_bytes){ msg_store_bytes = db.msg_store_bytes; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", msg_store_bytes); - db__messages_easy_queue(NULL, "$SYS/broker/store/messages/bytes", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/store/messages/bytes", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(db.subscription_count != subscription_count){ subscription_count = db.subscription_count; len = (uint32_t)snprintf(buf, BUFLEN, "%d", subscription_count); - db__messages_easy_queue(NULL, "$SYS/broker/subscriptions/count", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/subscriptions/count", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(db.shared_subscription_count != shared_subscription_count){ shared_subscription_count = db.shared_subscription_count; len = (uint32_t)snprintf(buf, BUFLEN, "%d", shared_subscription_count); - db__messages_easy_queue(NULL, "$SYS/broker/shared_subscriptions/count", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/shared_subscriptions/count", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(db.retained_count != retained_count){ retained_count = db.retained_count; len = (uint32_t)snprintf(buf, BUFLEN, "%d", retained_count); - db__messages_easy_queue(NULL, "$SYS/broker/retained messages/count", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/retained messages/count", SYS_TREE_QOS, len, buf, 1, 0, NULL); } #ifdef REAL_WITH_MEMORY_TRACKING @@ -322,55 +323,55 @@ void sys_tree__update(int interval, time_t start_time) if(msgs_received != g_msgs_received){ msgs_received = g_msgs_received; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", msgs_received); - db__messages_easy_queue(NULL, "$SYS/broker/messages/received", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/messages/received", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(msgs_sent != g_msgs_sent){ msgs_sent = g_msgs_sent; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", msgs_sent); - db__messages_easy_queue(NULL, "$SYS/broker/messages/sent", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/messages/sent", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(publish_dropped != g_msgs_dropped){ publish_dropped = g_msgs_dropped; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", publish_dropped); - db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/dropped", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/dropped", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(pub_msgs_received != g_pub_msgs_received){ pub_msgs_received = g_pub_msgs_received; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", pub_msgs_received); - db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/received", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/received", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(pub_msgs_sent != g_pub_msgs_sent){ pub_msgs_sent = g_pub_msgs_sent; len = (uint32_t)snprintf(buf, BUFLEN, "%lu", pub_msgs_sent); - db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/sent", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/publish/messages/sent", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(bytes_received != g_bytes_received){ bytes_received = g_bytes_received; len = (uint32_t)snprintf(buf, BUFLEN, "%llu", bytes_received); - db__messages_easy_queue(NULL, "$SYS/broker/bytes/received", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/bytes/received", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(bytes_sent != g_bytes_sent){ bytes_sent = g_bytes_sent; len = (uint32_t)snprintf(buf, BUFLEN, "%llu", bytes_sent); - db__messages_easy_queue(NULL, "$SYS/broker/bytes/sent", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/bytes/sent", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(pub_bytes_received != g_pub_bytes_received){ pub_bytes_received = g_pub_bytes_received; len = (uint32_t)snprintf(buf, BUFLEN, "%llu", pub_bytes_received); - db__messages_easy_queue(NULL, "$SYS/broker/publish/bytes/received", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/publish/bytes/received", SYS_TREE_QOS, len, buf, 1, 0, NULL); } if(pub_bytes_sent != g_pub_bytes_sent){ pub_bytes_sent = g_pub_bytes_sent; len = (uint32_t)snprintf(buf, BUFLEN, "%llu", pub_bytes_sent); - db__messages_easy_queue(NULL, "$SYS/broker/publish/bytes/sent", SYS_TREE_QOS, len, buf, 1, 60, NULL); + db__messages_easy_queue(NULL, "$SYS/broker/publish/bytes/sent", SYS_TREE_QOS, len, buf, 1, 0, NULL); } last_update = db.now_s; diff --git a/test/broker/01-bad-initial-packets.py b/test/broker/01-bad-initial-packets.py new file mode 100755 index 00000000..7e2ca777 --- /dev/null +++ b/test/broker/01-bad-initial-packets.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# Test whether non-CONNECT packets as an initial packet can cause excess memory use + +from mosq_test_helper import * +import psutil + +def write_config(filename, port): + with open(filename, 'w') as f: + f.write(f"listener {port}\n") + f.write("allow_anonymous true\n") + f.write("sys_interval 1\n") + +def do_send(port, socks, payload): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socks.append(sock) + sock.connect(("127.0.0.1", port)) + try: + sock.send(payload) + except ConnectionResetError: + pass + +def do_test(port): + rc = 1 + + conf_file = os.path.basename(__file__).replace('.py', '.conf') + write_config(conf_file, port) + broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port, use_conf=True) + + try: + socks = [] + + do_send(port, socks, b"\x20\x80\x80\x80t" + b"\01"*100000000) # CONNACK + do_send(port, socks, b"\x30\x80\x80\x80t" + b"\01"*100000000) # PUBLISH + do_send(port, socks, b"\x40\x80\x80\x80t" + b"\01"*100000000) # PUBACK + do_send(port, socks, b"\x50\x80\x80\x80t" + b"\01"*100000000) # PUBREC + do_send(port, socks, b"\x60\x80\x80\x80t" + b"\01"*100000000) # PUBREL + do_send(port, socks, b"\x70\x80\x80\x80t" + b"\01"*100000000) # PUBCOMP + do_send(port, socks, b"\x80\x80\x80\x80t" + b"\01"*100000000) # SUBSCRIBE + do_send(port, socks, b"\x90\x80\x80\x80t" + b"\01"*100000000) # SUBACK + do_send(port, socks, b"\xA0\x80\x80\x80t" + b"\01"*100000000) # UNSUBSCRIBE + do_send(port, socks, b"\xB0\x80\x80\x80t" + b"\01"*100000000) # UNSUBACK + do_send(port, socks, b"\xC0\x80\x80\x80t" + b"\01"*100000000) # PINGREQ + do_send(port, socks, b"\xD0\x80\x80\x80t" + b"\01"*100000000) # PINGRESP + do_send(port, socks, b"\xE0\x80\x80\x80t" + b"\01"*100000000) # DISCONNECT + do_send(port, socks, b"\xF0\x80\x80\x80t" + b"\01"*100000000) # AUTH + + mem = psutil.Process(broker.pid).memory_info().vms + + for s in socks: + s.close() + + if os.environ.get('MOSQ_USE_VALGRIND') is None: + limit = 25000000 + else: + limit = 120000000 + if mem > limit: + raise mosq_test.TestError(f"Process memory {mem} greater than limit of {limit}") + + rc = 0 + except MemoryError: + print("Memory error!") + except Exception as e: + print(e) + 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) + + +port = mosq_test.get_port() + +do_test(port) + +exit(0) diff --git a/test/broker/03-publish-qos2-dup.py b/test/broker/03-publish-qos2-dup.py new file mode 100755 index 00000000..70834fab --- /dev/null +++ b/test/broker/03-publish-qos2-dup.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +from mosq_test_helper import * + +def do_test(proto_ver): + rc = 1 + connect_packet = mosq_test.gen_connect("03-pub-qos2-dup-test", proto_ver=proto_ver) + connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver) + + mid = 1 + publish_packet = mosq_test.gen_publish("topic", qos=2, mid=mid, payload="message", proto_ver=proto_ver, dup=1) + pubrec_packet = mosq_test.gen_pubrec(mid, proto_ver=proto_ver) + + disconnect_packet = mosq_test.gen_disconnect(reason_code=130, proto_ver=proto_ver) + + port = mosq_test.get_port() + broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port) + + try: + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "pubrec 1") + mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "pubrec 2") + if proto_ver == 5: + mosq_test.do_send_receive(sock, publish_packet, disconnect_packet, "disconnect") + rc = 0 + else: + try: + mosq_test.do_send_receive(sock, publish_packet, b"", "disconnect1") + rc = 0 + except BrokenPipeError: + rc = 0 + + sock.close() + except Exception as e: + print(e) + except mosq_test.TestError: + pass + finally: + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + print("proto_ver=%d" % (proto_ver)) + exit(rc) + + +def all_tests(): + rc = do_test(proto_ver=4) + if rc: + return rc; + rc = do_test(proto_ver=5) + if rc: + return rc; + return 0 + +if __name__ == '__main__': + all_tests() diff --git a/test/broker/04-retain-clear-multiple.py b/test/broker/04-retain-clear-multiple.py new file mode 100755 index 00000000..be185a21 --- /dev/null +++ b/test/broker/04-retain-clear-multiple.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +# Exercise multi-level retain clearing + +from mosq_test_helper import * + +def send_retain(port, topic, payload): + connect_packet = mosq_test.gen_connect("retain-clear-test") + connack_packet = mosq_test.gen_connack(rc=0) + + publish_packet = mosq_test.gen_publish(topic, qos=1, mid=1, payload=payload, retain=True) + puback_packet = mosq_test.gen_puback(mid=1) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=4, port=port) + mosq_test.do_send_receive(sock, publish_packet, puback_packet, f"set retain {topic}") + sock.close() + +def do_test(): + rc = 1 + connect_packet = mosq_test.gen_connect("retain-clear-test") + connack_packet = mosq_test.gen_connack(rc=0) + + subscribe_packet = mosq_test.gen_subscribe(1, "#", 0) + suback_packet = mosq_test.gen_suback(1, 0) + + retain1_packet = mosq_test.gen_publish("1/2/3/4/5/6/7", qos=0, payload="retained message", retain=True) + retain2_packet = mosq_test.gen_publish("1/2/3/4", qos=0, payload="retained message", retain=True) + retain3_packet = mosq_test.gen_publish("1", qos=0, payload="retained message", retain=True) + + port = mosq_test.get_port() + broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port) + + try: + send_retain(port, "1/2/3/4/5/6/7", "retained message") + send_retain(port, "1/2/3/4", "retained message") + send_retain(port, "1", "retained message") + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback") + mosq_test.expect_packet(sock, "retain3", retain3_packet) + mosq_test.expect_packet(sock, "retain2", retain2_packet) + mosq_test.expect_packet(sock, "retain1", retain1_packet) + sock.close() + + send_retain(port, "1/2/3/4", None) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback") + mosq_test.expect_packet(sock, "retain3", retain3_packet) + mosq_test.expect_packet(sock, "retain1", retain1_packet) + sock.close() + + send_retain(port, "1/2/3/4/5/6/7", None) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback") + mosq_test.expect_packet(sock, "retain3", retain3_packet) + sock.close() + + send_retain(port, "1", None) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback") + mosq_test.do_ping(sock) + sock.close() + + rc = 0 + except mosq_test.TestError: + pass + finally: + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + exit(rc) + +do_test() +exit(0) + diff --git a/test/broker/06-bridge-reconnect-local-out.py b/test/broker/06-bridge-reconnect-local-out.py index 887470b6..b5adba8f 100755 --- a/test/broker/06-bridge-reconnect-local-out.py +++ b/test/broker/06-bridge-reconnect-local-out.py @@ -17,6 +17,7 @@ def write_config(filename, port1, port2, protocol_version): f.write("address 127.0.0.1:%d\n" % (port1)) f.write("topic bridge/# out\n") f.write("bridge_protocol_version %s\n" % (protocol_version)) + f.write("cleansession false\n") def do_test(proto_ver): diff --git a/test/broker/07-will-control.py b/test/broker/07-will-control.py new file mode 100755 index 00000000..68d2eccb --- /dev/null +++ b/test/broker/07-will-control.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +# Test whether a client setting a will with $CONTROL in is denied + +from mosq_test_helper import * + + +def do_test(start_broker, proto_ver): + rc = 1 + mid = 1 + connect_packet = mosq_test.gen_connect("will", will_topic="$CONTROL/dynamic-security/v1", will_payload=b"will-message", proto_ver=proto_ver) + + port = mosq_test.get_port() + if start_broker: + broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port) + + try: + sock = mosq_test.client_connect_only(port=port) + sock.send(connect_packet) + d = sock.recv(1) + if d == b"": + rc = 0 + + sock.close() + except mosq_test.TestError: + pass + except Exception as e: + print(e) + finally: + if start_broker: + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + exit(rc) + else: + return rc + + +def all_tests(start_broker=False): + rc = do_test(start_broker, proto_ver=4) + if rc: + return rc; + rc = do_test(start_broker, proto_ver=5) + if rc: + return rc; + return 0 + +if __name__ == '__main__': + all_tests(True) diff --git a/test/broker/Makefile b/test/broker/Makefile index 63b9ae8f..e66c7ffc 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -23,6 +23,7 @@ msg_sequence_test: ./msg_sequence_test.py 01 : + ./01-bad-initial-packets.py ./01-connect-575314.py ./01-connect-allow-anonymous.py ./01-connect-disconnect-v5.py @@ -83,6 +84,7 @@ msg_sequence_test: ./03-publish-qos1-no-subscribers-v5.py ./03-publish-qos1-retain-disabled.py ./03-publish-qos1.py + ./03-publish-qos2-dup.py ./03-publish-qos2-max-inflight.py ./03-publish-qos2.py @@ -90,6 +92,7 @@ msg_sequence_test: ./04-retain-check-source-persist-diff-port.py ./04-retain-check-source-persist.py ./04-retain-check-source.py + ./04-retain-clear-multiple.py ./04-retain-qos0-clear.py ./04-retain-qos0-fresh.py ./04-retain-qos0-repeated.py @@ -124,6 +127,7 @@ msg_sequence_test: ./06-bridge-reconnect-local-out.py 07 : + ./07-will-control.py ./07-will-delay-invalid-573191.py ./07-will-delay-reconnect.py ./07-will-delay-recover.py diff --git a/test/broker/test.py b/test/broker/test.py index e034a83d..e8956408 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -5,6 +5,7 @@ import ptest tests = [ #(ports required, 'path'), + (1, './01-bad-initial-packets.py'), (1, './01-connect-575314.py'), (1, './01-connect-allow-anonymous.py'), (1, './01-connect-disconnect-v5.py'), @@ -63,11 +64,13 @@ tests = [ (1, './03-publish-qos1-no-subscribers-v5.py'), (1, './03-publish-qos1-retain-disabled.py'), (1, './03-publish-qos1.py'), + (1, './03-publish-qos2-dup.py'), (1, './03-publish-qos2-max-inflight.py'), (1, './03-publish-qos2.py'), (1, './04-retain-check-source-persist.py'), (1, './04-retain-check-source.py'), + (1, './04-retain-clear-multiple.py'), (1, './04-retain-qos0-clear.py'), (1, './04-retain-qos0-fresh.py'), (1, './04-retain-qos0-repeated.py'), @@ -100,6 +103,7 @@ tests = [ (3, './06-bridge-per-listener-settings.py'), (2, './06-bridge-reconnect-local-out.py'), + (1, './07-will-control.py'), (1, './07-will-delay-invalid-573191.py'), (1, './07-will-delay-reconnect.py'), (1, './07-will-delay-recover.py'), diff --git a/test/mosq_test.py b/test/mosq_test.py index d9fe6970..98fd3ac9 100644 --- a/test/mosq_test.py +++ b/test/mosq_test.py @@ -70,7 +70,7 @@ def start_broker(filename, cmd=None, port=0, use_conf=False, expect_fail=False, print("FAIL: unable to start broker: %s" % errs) raise IOError else: - return None + return broker def start_client(filename, cmd, env, port=1888): if cmd is None: diff --git a/test/unit/Makefile b/test/unit/Makefile index d548f080..41bc6057 100644 --- a/test/unit/Makefile +++ b/test/unit/Makefile @@ -82,6 +82,10 @@ PERSIST_WRITE_OBJS = \ utf8_mosq.o \ util_mosq.o +TLS_TEST_OBJS = \ + tls_test.o \ + tls_stubs.o + SUBS_TEST_OBJS = \ subs_test.o \ subs_stubs.o @@ -112,6 +116,9 @@ persist_write_test : ${PERSIST_WRITE_TEST_OBJS} ${PERSIST_WRITE_OBJS} subs_test : ${SUBS_TEST_OBJS} ${SUBS_OBJS} $(CROSS_COMPILE)$(CC) $(LDFLAGS) -o $@ $^ $(LDADD) +tls_test : ${TLS_TEST_OBJS} ${TLS_OBJS} + $(CROSS_COMPILE)$(CC) $(LDFLAGS) -o $@ $^ $(LDADD) -lssl -lcrypto + bridge_topic.o : ../../src/bridge_topic.c $(CROSS_COMPILE)$(CC) $(CPPFLAGS) $(CFLAGS) -DWITH_BROKER -DWITH_BRIDGE -c -o $@ $^ @@ -167,10 +174,11 @@ util_topic.o : ../../lib/util_topic.c utf8_mosq.o : ../../lib/utf8_mosq.c $(CROSS_COMPILE)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $^ -build : mosq_test bridge_topic_test persist_read_test persist_write_test subs_test +build : mosq_test bridge_topic_test persist_read_test persist_write_test subs_test tls_test test-lib : build ./mosq_test + ./tls_test test-broker : build ./bridge_topic_test diff --git a/test/unit/tls_stubs.c b/test/unit/tls_stubs.c new file mode 100644 index 00000000..c865cbb1 --- /dev/null +++ b/test/unit/tls_stubs.c @@ -0,0 +1,40 @@ +#include "config.h" + +#include +#include + +int tls_ex_index_mosq; + +struct mosquitto_db{ + +}; + +int log__printf(struct mosquitto *mosq, unsigned int priority, const char *fmt, ...) +{ + UNUSED(mosq); + UNUSED(priority); + UNUSED(fmt); + + return 0; +} + +time_t mosquitto_time(void) +{ + return 123; +} + +int net__socket_close(struct mosquitto_db *db, struct mosquitto *mosq) +{ + UNUSED(db); + UNUSED(mosq); + + return MOSQ_ERR_SUCCESS; +} + +int send__pingreq(struct mosquitto *mosq) +{ + UNUSED(mosq); + + return MOSQ_ERR_SUCCESS; +} + diff --git a/test/unit/tls_test.c b/test/unit/tls_test.c new file mode 100644 index 00000000..26dd36a3 --- /dev/null +++ b/test/unit/tls_test.c @@ -0,0 +1,102 @@ +#include +#include + +#define WITH_TLS + +#include "tls_mosq.c" + +//static int mosquitto__cmp_hostname_wildcard(char *certname, const char *hostname) + +void hostname_cmp_helper(char *certname, const char *hostname, int expected) +{ + int rc = mosquitto__cmp_hostname_wildcard(certname, hostname); + CU_ASSERT_EQUAL(rc, expected); + if(rc != expected){ + printf("%d || %d\n", rc, expected); + } +} + +void TEST_tls_hostname_compare_null(void) +{ + hostname_cmp_helper(NULL, "localhost", 1); + hostname_cmp_helper("localhost", NULL, 1); + hostname_cmp_helper(NULL, NULL, 1); +} + + +void TEST_tls_hostname_compare_simple(void) +{ + hostname_cmp_helper("localhost", "localhost", 0); + hostname_cmp_helper("localhost", "localhose", 15); +} + + +void TEST_tls_hostname_compare_bad_wildcard_format(void) +{ + hostname_cmp_helper("**localhost", "localhost", 1); + hostname_cmp_helper("*,localhost", "localhost", 1); + hostname_cmp_helper("*.", "localhost", 1); +} + + +void TEST_tls_hostname_compare_invalid_wildcard(void) +{ + hostname_cmp_helper("*.com", "example.com", 1); + hostname_cmp_helper("*.com", "example.org", 1); + hostname_cmp_helper("*.org", "example.org", 1); +} + + +void TEST_tls_hostname_compare_good_wildcard(void) +{ + hostname_cmp_helper("*.example.com", "test.example.com", 0); + hostname_cmp_helper("*.example.com", "test.example.org", -12); + hostname_cmp_helper("*.example.org", "test.example.org", 0); +} + + +/* ======================================================================== + * TEST SUITE SETUP + * ======================================================================== */ + + +int main(int argc, char *argv[]) +{ + CU_pSuite test_suite = NULL; + unsigned int fails; + + UNUSED(argc); + UNUSED(argv); + + if(CU_initialize_registry() != CUE_SUCCESS){ + printf("Error initializing CUnit registry.\n"); + return 1; + } + + test_suite = CU_add_suite("Subs", NULL, NULL); + if(!test_suite){ + printf("Error adding CUnit TLS test suite.\n"); + CU_cleanup_registry(); + return 1; + } + + if(0 + || !CU_add_test(test_suite, "TLS hostname compare null", TEST_tls_hostname_compare_null) + || !CU_add_test(test_suite, "TLS hostname compare simple", TEST_tls_hostname_compare_simple) + || !CU_add_test(test_suite, "TLS hostname compare bad wildcard format", TEST_tls_hostname_compare_bad_wildcard_format) + || !CU_add_test(test_suite, "TLS hostname compare invalid wildcard", TEST_tls_hostname_compare_invalid_wildcard) + || !CU_add_test(test_suite, "TLS hostname compare good wildcard", TEST_tls_hostname_compare_good_wildcard) + ){ + + printf("Error adding TLS CUnit tests.\n"); + CU_cleanup_registry(); + return 1; + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + fails = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return (int)fails; +} diff --git a/test/unit/utf8.c b/test/unit/utf8.c index c58e1529..a3f9508c 100644 --- a/test/unit/utf8.c +++ b/test/unit/utf8.c @@ -415,10 +415,10 @@ void TEST_utf8_control_characters(void) buf[1] = '\0'; utf8_helper((char *)buf, MOSQ_ERR_MALFORMED_UTF8); - /* U+007F to U+009F are two byte control characters */ + /* U+0080 to U+009F are two byte control characters */ for(i=0x80; i<0xA0; i++){ buf[0] = 0xC2; - buf[1] = (uint8_t)(i-0x80); + buf[1] = (uint8_t)i; buf[2] = '\0'; utf8_helper((char *)buf, MOSQ_ERR_MALFORMED_UTF8); } diff --git a/www/pages/security.md b/www/pages/security.md index 2f124cca..94768ea5 100644 --- a/www/pages/security.md +++ b/www/pages/security.md @@ -19,6 +19,9 @@ follow the steps on [Eclipse Security] page to report it. Listed with most recent first. Further information on security related issues can be found in the [security category]. +* June 2023: [CVE-2023-28366]: Clients sending unacknowledged QoS 2 messages + with duplicate message ids cause a memory leak. Affecting versions **1.3.2** + to **2.0.15** inclusive, fixed in **2.0.16**. * August 2022: Deleting the anonymous group in the dynamic security plugin could lead to a crash. Affecting versions **2.0.0** to **2.0.14** inclusive, fixed in **2.0.15**. @@ -62,14 +65,14 @@ can be found in the [security category]. inclusive, fixed in **1.4.12**. More details at [security-advisory-cve-2017-7650]. -[version-166-released]: /2019/09/version-1-6-6-released/ -[version-162-released]: /2019/04/version-1-6-2-released/ -[version-155-released]: /2018/11/version-155-released/ -[version-154-released]: /2018/11/version-154-released/ -[security-advisory-cve-2018-12543]: /2018/09/security-advisory-cve-2018-12543/ -[security-advisory-cve-2017-7651-cve-2017-7652]: /2018/02/security-advisory-cve-2017-7651-cve-2017-7652/ -[security-advisory-cve-2017-7650]: /2017/05/security-advisory-cve-2017-7650/ -[security-advisory-cve-2017-9868]: /2017/06/security-advisory-cve-2017-9868/ +[version-166-released]: /blog/2019/09/version-1-6-6-released/ +[version-162-released]: /blog/2019/04/version-1-6-2-released/ +[version-155-released]: /blog/2018/11/version-155-released/ +[version-154-released]: /blog/2018/11/version-154-released/ +[security-advisory-cve-2018-12543]: /blog/2018/09/security-advisory-cve-2018-12543/ +[security-advisory-cve-2017-7651-cve-2017-7652]: /blog/2018/02/security-advisory-cve-2017-7651-cve-2017-7652/ +[security-advisory-cve-2017-7650]: /blog/2017/05/security-advisory-cve-2017-7650/ +[security-advisory-cve-2017-9868]: /blog/2017/06/security-advisory-cve-2017-9868/ [Eclipse Security]: https://www.eclipse.org/security/ [security category]: /blog/categories/security/ @@ -81,9 +84,9 @@ can be found in the [security category]. [CVE-2018-20145]: https://nvd.nist.gov/vuln/detail/CVE-2018-20145 [CVE-2018-12543]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12543 [CVE-2017-9868]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9868 -[CVE-2017-7655]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7652 -[CVE-2017-7654]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7652 -[CVE-2017-7653]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7652 +[CVE-2017-7655]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7655 +[CVE-2017-7654]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7654 +[CVE-2017-7653]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7653 [CVE-2017-7652]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7652 [CVE-2017-7651]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7651 [CVE-2017-7650]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7650 diff --git a/www/posts/2015/12/using-lets-encrypt-certificates-with-mosquitto.md b/www/posts/2015/12/using-lets-encrypt-certificates-with-mosquitto.md index 22eed9c9..069ecb90 100644 --- a/www/posts/2015/12/using-lets-encrypt-certificates-with-mosquitto.md +++ b/www/posts/2015/12/using-lets-encrypt-certificates-with-mosquitto.md @@ -17,13 +17,14 @@ Then use the following for your mosquitto.conf: ``` listener 8883 -cafile /etc/ssl/certs/DST_Root_CA_X3.pem +cafile /etc/ssl/certs/ISRG_Root_X1.pem certfile /etc/letsencrypt/live/example.com/fullchain.pem keyfile /etc/letsencrypt/live/example.com/privkey.pem ``` -You need to be aware that current versions of mosquitto never update listener -settings when running, so when you regenerate the server certificates you will -need to completely restart the broker. +Since version 2.0 of Mosquitto, you can send a SIGHUP to the broker to cause it +to reload certificates. Prior to this version, mosquitto would never update +listener settings when running, so you will need to completely restart the +broker. [Let's Encrypt]: https://letsencrypt.org/