diff --git a/.github/workflows/delete-old-workflow-runs.yml b/.github/workflows/delete-old-workflow-runs.yml new file mode 100644 index 00000000..29f9ca05 --- /dev/null +++ b/.github/workflows/delete-old-workflow-runs.yml @@ -0,0 +1,18 @@ +name: Delete old workflow runs +on: + workflow_dispatch: + schedule: + - cron: '0 0 1 * *' +# Run monthly, at 00:00 on the 1st day of month. + +jobs: + del_runs: + runs-on: ubuntu-latest + steps: + - name: Delete workflow runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ github.token }} + repository: ${{ github.repository }} + retain_days: 30 + keep_minimum_runs: 6 diff --git a/.gitignore b/.gitignore index f5f98fb6..cc71bce9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# .gitignore *.a *.db *.gcda @@ -112,9 +113,25 @@ test/unit/lib/lib_test test/unit/broker/persist_read_test test/unit/broker/persist_write_test test/unit/broker/subs_test +test/unit/tls_test test/unit/out/ www/cache/ __pycache__ - *.sync-conflict-* +# Debian generated files +debian/.debhelper/ +debian/debhelper-build-stamp +debian/files +debian/*.log +debian/*.substvars +debian/*mosquitto*/ +debian/*.debhelper +debian/tmp/ +obj-*/ + +# Emacs generated files +*~ + +# Others +tmp/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 26e30271..a37fe6ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ project(mosquitto LANGUAGES C CXX ) -set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") diff --git a/ChangeLog.txt b/ChangeLog.txt index 79e23ab6..bd015725 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -189,6 +189,77 @@ Build: - cJSON is now a required dependency. +2.0.17 - 2023-08-22 +=================== + +Broker: +- Fix `max_queued_messages 0` stopping clients from receiving messages. + Closes #2879. +- Fix `max_inflight_messages` not being set correctly. Closes #2876. + +Apps: +- Fix `mosquitto_passwd -U` backup file creation. Closes #2873. + + +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/apps/db_dump/db_dump.c b/apps/db_dump/db_dump.c index 94125617..aba54bb7 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 5a748a49..66ed5740 100644 --- a/apps/mosquitto_ctrl/CMakeLists.txt +++ b/apps/mosquitto_ctrl/CMakeLists.txt @@ -11,6 +11,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 ../../common/json_help.c ../../common/json_help.h diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index de4e2139..8007a853 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 @@ -32,6 +34,7 @@ Contributors: #include "json_help.h" #include "password_mosq.h" #include "get_password.h" +#include "misc_mosq.h" #define MAX_STRING_LEN 4096 @@ -709,13 +712,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"); @@ -724,7 +720,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 ca752712..5f022962 100644 --- a/apps/mosquitto_ctrl/options.c +++ b/apps/mosquitto_ctrl/options.c @@ -611,6 +611,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){ @@ -633,11 +638,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 9bec4096..399bccaa 100644 --- a/apps/mosquitto_passwd/mosquitto_passwd.c +++ b/apps/mosquitto_passwd/mosquitto_passwd.c @@ -364,15 +364,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); @@ -597,7 +609,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); @@ -609,20 +621,21 @@ 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); + size_t len = strlen(password_file) + strlen(".backup.XXXXXX") + 1; + backup_file = malloc(len); 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, len, "%s.backup.XXXXXX", password_file); free(password_file); password_file = NULL; diff --git a/common/misc_mosq.c b/common/misc_mosq.c index b8a1944c..36a9a9b4 100644 --- a/common/misc_mosq.c +++ b/common/misc_mosq.c @@ -38,6 +38,8 @@ Contributors: # define PATH_MAX MAX_PATH #else # include +# include +# include # include #endif @@ -149,6 +151,60 @@ FILE *mosquitto__fopen(const char *path, const char *mode, bool restrict_read) 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); diff --git a/common/password_mosq.c b/common/password_mosq.c index b023880e..5547f1a4 100644 --- a/common/password_mosq.c +++ b/common/password_mosq.c @@ -124,9 +124,7 @@ int pw__memcmp_const(const void *a, const void *b, size_t len) if(!a || !b) return 1; for(i=0; i0 && 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/docker/1.5-openssl/Dockerfile b/docker/1.5-openssl/Dockerfile index 2d258cbd..69ade540 100644 --- a/docker/1.5-openssl/Dockerfile +++ b/docker/1.5-openssl/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.14 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" @@ -6,16 +6,19 @@ LABEL maintainer="Roger Light " \ ENV VERSION=1.5.11 \ DOWNLOAD_SHA256=4a3b8a8f5505d27a7a966dd68bfd76f1e69feb51796d1b46b7271d1bb5a1a299 \ GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 \ - LWS_VERSION=2.4.2 + LWS_VERSION=4.2.1 \ + LWS_SHA256=842da21f73ccba2be59e680de10a8cce7928313048750eb6ad73b6fa50763c51 RUN set -x && \ apk --no-cache add --virtual build-deps \ build-base \ cmake \ gnupg \ + linux-headers \ openssl-dev \ util-linux-dev && \ wget https://github.com/warmcat/libwebsockets/archive/v${LWS_VERSION}.tar.gz -O /tmp/lws.tar.gz && \ + echo "$LWS_SHA256 /tmp/lws.tar.gz" | sha256sum -c - && \ mkdir -p /build/lws && \ tar --strip=1 -xf /tmp/lws.tar.gz -C /build/lws && \ rm /tmp/lws.tar.gz && \ @@ -23,11 +26,13 @@ RUN set -x && \ cmake . \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DCMAKE_INSTALL_PREFIX=/usr \ + -DDISABLE_WERROR=ON \ -DLWS_IPV6=ON \ -DLWS_WITHOUT_BUILTIN_GETIFADDRS=ON \ -DLWS_WITHOUT_CLIENT=ON \ -DLWS_WITHOUT_EXTENSIONS=ON \ -DLWS_WITHOUT_TESTAPPS=ON \ + -DLWS_WITH_EXTERNAL_POLL=ON \ -DLWS_WITH_HTTP2=OFF \ -DLWS_WITH_SHARED=OFF \ -DLWS_WITH_ZIP_FOPS=OFF \ diff --git a/docker/1.6-openssl/Dockerfile b/docker/1.6-openssl/Dockerfile index 025d85f1..8c98d8ad 100644 --- a/docker/1.6-openssl/Dockerfile +++ b/docker/1.6-openssl/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.14 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" diff --git a/docker/2.0-openssl/Dockerfile b/docker/2.0-openssl/Dockerfile index d0eb1ebb..5219bf6f 100644 --- a/docker/2.0-openssl/Dockerfile +++ b/docker/2.0-openssl/Dockerfile @@ -1,10 +1,10 @@ -FROM alpine:3.16 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" -ENV VERSION=2.0.15 \ - DOWNLOAD_SHA256=4735b1d32e3f91c7a8896741d88a3022e89730a1ee897946decfa0df27039ac6 \ +ENV VERSION=2.0.17 \ + DOWNLOAD_SHA256=3be7a911236567c1a9fbe25baf3e3167004ba4a0c151a448ef1f7fc077dba52f \ GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 \ LWS_VERSION=4.2.1 \ LWS_SHA256=842da21f73ccba2be59e680de10a8cce7928313048750eb6ad73b6fa50763c51 diff --git a/docker/2.0/Dockerfile b/docker/2.0/Dockerfile index a1056a4c..8241190d 100644 --- a/docker/2.0/Dockerfile +++ b/docker/2.0/Dockerfile @@ -1,10 +1,10 @@ -FROM alpine:3.16 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" -ENV VERSION=2.0.15 \ - DOWNLOAD_SHA256=4735b1d32e3f91c7a8896741d88a3022e89730a1ee897946decfa0df27039ac6 \ +ENV VERSION=2.0.17 \ + DOWNLOAD_SHA256=3be7a911236567c1a9fbe25baf3e3167004ba4a0c151a448ef1f7fc077dba52f \ GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 \ LWS_VERSION=4.2.1 \ LWS_SHA256=842da21f73ccba2be59e680de10a8cce7928313048750eb6ad73b6fa50763c51 diff --git a/docker/generic/Dockerfile b/docker/generic/Dockerfile index 656f8a15..b7970bfb 100644 --- a/docker/generic/Dockerfile +++ b/docker/generic/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.14 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" diff --git a/docker/local/Dockerfile b/docker/local/Dockerfile index 6aa5b22c..8efeb4c8 100644 --- a/docker/local/Dockerfile +++ b/docker/local/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.16 +FROM alpine:3.18 LABEL maintainer="Roger Light " \ description="Eclipse Mosquitto MQTT Broker" diff --git a/include/mosquitto.h b/include/mosquitto.h index 16c3f65f..5c1536ed 100644 --- a/include/mosquitto.h +++ b/include/mosquitto.h @@ -535,8 +535,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: @@ -567,8 +567,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, @@ -611,8 +611,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, @@ -652,8 +652,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: @@ -687,8 +687,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, @@ -726,8 +726,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/include/mosquitto_broker.h b/include/mosquitto_broker.h index d40998b3..0e4a908c 100644 --- a/include/mosquitto_broker.h +++ b/include/mosquitto_broker.h @@ -112,7 +112,7 @@ struct mosquitto_client_msg { uint16_t mid; uint8_t qos; bool retain; - bool dup; + uint8_t dup; uint8_t direction; uint8_t state; uint8_t padding[5]; diff --git a/lib/loop.c b/lib/loop.c index 6be75ee7..6c2082e7 100644 --- a/lib/loop.c +++ b/lib/loop.c @@ -65,18 +65,20 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets) if(net__is_connected(mosq)){ maxfd = mosq->sock; FD_SET(mosq->sock, &readfds); - pthread_mutex_lock(&mosq->out_packet_mutex); - if(mosq->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->out_packet_mutex); + if(mosq->out_packet){ + FD_SET(mosq->sock, &writefds); + } + pthread_mutex_unlock(&mosq->out_packet_mutex); } } -#endif - pthread_mutex_unlock(&mosq->out_packet_mutex); }else{ #ifdef WITH_SRV if(mosq->achan){ diff --git a/lib/mosquitto.c b/lib/mosquitto.c index 60f0d2c0..af18b9bf 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; @@ -332,18 +335,7 @@ int mosquitto_socket(struct mosquitto *mosq) bool mosquitto_want_write(struct mosquitto *mosq) { - bool result = false; - if(mosq->out_packet){ - result = true; - } -#ifdef WITH_TLS - if(mosq->ssl){ - if (mosq->want_write) { - result = true; - } - } -#endif - return result; + return mosq->out_packet || mosq->want_write; } diff --git a/lib/net_mosq.c b/lib/net_mosq.c index 0b21981d..1a25ab6d 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -181,6 +181,10 @@ void net__init_tls(void) SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); +# else + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS \ + | OPENSSL_INIT_LOAD_CONFIG, NULL); # endif #if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 ENGINE_load_builtin_engines(); diff --git a/lib/options.c b/lib/options.c index 9b2ef9ed..4bbe4ebc 100644 --- a/lib/options.c +++ b/lib/options.c @@ -168,14 +168,21 @@ int mosquitto_tls_set(struct mosquitto *mosq, const char *cafile, const char *ca mosquitto__FREE(mosq->tls_keyfile); if(keyfile){ - fptr = mosquitto__fopen(keyfile, "rt", false); - if(fptr){ - fclose(fptr); - }else{ - mosquitto__FREE(mosq->tls_cafile); - mosquitto__FREE(mosq->tls_capath); - mosquitto__FREE(mosq->tls_certfile); - return MOSQ_ERR_INVAL; + 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_certfile); + mosq->tls_certfile = NULL; + return MOSQ_ERR_INVAL; + } } mosq->tls_keyfile = mosquitto__strdup(keyfile); if(!mosq->tls_keyfile){ @@ -223,6 +230,14 @@ int mosquitto_tls_opts_set(struct mosquitto *mosq, int cert_reqs, const char *tl 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; + } mosquitto__FREE(mosq->tls_ciphers); mosquitto__FREE(mosq->tls_13_ciphers); @@ -278,6 +293,11 @@ int mosquitto_string_option(struct mosquitto *mosq, enum mosq_opt_t option, cons #if defined(WITH_TLS) && !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 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 720e1ee3..da1f30e3 100644 --- a/lib/packet_mosq.c +++ b/lib/packet_mosq.c @@ -137,6 +137,20 @@ void packet__cleanup_all(struct mosquitto *mosq) static void packet__queue_append(struct mosquitto *mosq, struct mosquitto__packet *packet) { +#ifdef WITH_BROKER + if(db.config->max_queued_messages > 0 && 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); + } + metrics__int_inc(mosq_counter_mqtt_publish_dropped, 1); + return; + } +#endif + pthread_mutex_lock(&mosq->out_packet_mutex); if(mosq->out_packet){ mosq->out_packet_last->next = packet; @@ -296,6 +310,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; } @@ -383,7 +399,7 @@ int packet__read(struct mosquitto *mosq) #ifdef WITH_BROKER metrics__int_inc(mosq_counter_bytes_received, 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 56306263..30113b35 100644 --- a/lib/property_mosq.c +++ b/lib/property_mosq.c @@ -26,6 +26,7 @@ Contributors: # include #endif +#include "logging_mosq.h" #include "memory_mosq.h" #include "mqtt_protocol.h" #include "packet_mosq.h" @@ -147,6 +148,9 @@ static int property__read(struct mosquitto__packet_in *packet, uint32_t *len, mo break; default: +#ifdef WITH_BROKER + log__printf(NULL, MOSQ_LOG_DEBUG, "Unsupported property type: %d", property_identifier); +#endif return MOSQ_ERR_MALFORMED_PACKET; } @@ -352,6 +356,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; } @@ -1223,12 +1230,10 @@ BROKER_EXPORT int mosquitto_property_copy_all(mosquitto_property **dest, const m case MQTT_PROP_TYPE_STRING: pnew->value.s.len = src->value.s.len; - if(src->value.s.v){ - pnew->value.s.v = strdup(src->value.s.v); - if(!pnew->value.s.v){ - mosquitto_property_free_all(dest); - return MOSQ_ERR_NOMEM; - } + 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; } break; @@ -1246,21 +1251,17 @@ BROKER_EXPORT int mosquitto_property_copy_all(mosquitto_property **dest, const m case MQTT_PROP_TYPE_STRING_PAIR: pnew->value.s.len = src->value.s.len; - if(src->value.s.v){ - pnew->value.s.v = strdup(src->value.s.v); - if(!pnew->value.s.v){ - mosquitto_property_free_all(dest); - return MOSQ_ERR_NOMEM; - } + 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; - if(src->name.v){ - pnew->name.v = strdup(src->name.v); - if(!pnew->name.v){ - mosquitto_property_free_all(dest); - return MOSQ_ERR_NOMEM; - } + 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; } break; diff --git a/lib/strings_mosq.c b/lib/strings_mosq.c index f92e6f1e..ec74ccf8 100644 --- a/lib/strings_mosq.c +++ b/lib/strings_mosq.c @@ -84,7 +84,7 @@ const char *mosquitto_strerror(int mosq_errno) case MOSQ_ERR_KEEPALIVE: return "Keepalive exceeded"; case MOSQ_ERR_LOOKUP: - return "Lookup failed"; + return "DNS Lookup failed"; case MOSQ_ERR_MALFORMED_PACKET: return "Malformed packet"; case MOSQ_ERR_DUPLICATE_PROPERTY: diff --git a/lib/tls_mosq.c b/lib/tls_mosq.c index 09624aa1..b1054757 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 873db395..7cc11e56 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -1638,9 +1638,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 diff --git a/man/mosquitto_ctrl.1.xml b/man/mosquitto_ctrl.1.xml index d66ac708..da088ac9 100644 --- a/man/mosquitto_ctrl.1.xml +++ b/man/mosquitto_ctrl.1.xml @@ -319,7 +319,7 @@ Disable Nagle's algorithm for the socket. This means that latency of sent messages is reduced, which is - particularly noticable for small, reasonably infrequent + particularly noticeable for small, reasonably infrequent messages. Using this option may result in more packets being sent than would normally be necessary. diff --git a/man/mosquitto_rr.1.xml b/man/mosquitto_rr.1.xml index 5113ef28..e542f920 100644 --- a/man/mosquitto_rr.1.xml +++ b/man/mosquitto_rr.1.xml @@ -715,6 +715,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 c3051ddb..f8f72f23 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -1069,7 +1069,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/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 54f23f88..3f12bcb8 100644 --- a/plugins/dynamic-security/auth.c +++ b/plugins/dynamic-security/auth.c @@ -42,9 +42,7 @@ static int memcmp_const(const void *a, const void *b, size_t len) if(!a || !b) return 1; for(i=0; iusername, username, username_len); - 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; } + int iterations; + const char *salt; + const char *password; json_get_int(j_client, "iterations", &iterations, 0, true); if(json_get_string(j_client, "salt", &salt, false) == MOSQ_ERR_SUCCESS && json_get_string(j_client, "password", &password, false) == MOSQ_ERR_SUCCESS diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index ec5986ec..4831e8ff 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -195,7 +195,6 @@ int dynsec_groups__config_load(struct dynsec__data *data, cJSON *tree) struct dynsec__group *group; struct dynsec__role *role; int priority; - const char *textname, *textdescription; const char *groupname; size_t groupname_len; @@ -211,6 +210,7 @@ int dynsec_groups__config_load(struct dynsec__data *data, cJSON *tree) cJSON_ArrayForEach(j_group, j_groups){ if(cJSON_IsObject(j_group) == true){ /* Group name */ + const char *groupname; if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ continue; } @@ -229,6 +229,7 @@ int dynsec_groups__config_load(struct dynsec__data *data, cJSON *tree) strncpy(group->groupname, groupname, groupname_len+1); /* Text name */ + const char *textname; if(json_get_string(j_group, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ if(textname){ group->text_name = strdup(textname); @@ -240,6 +241,7 @@ int dynsec_groups__config_load(struct dynsec__data *data, cJSON *tree) } /* Text description */ + const char *textdescription; if(json_get_string(j_group, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ if(textdescription){ group->text_description = strdup(textdescription); diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c index 98b3976f..cc91a2db 100644 --- a/plugins/dynamic-security/roles.c +++ b/plugins/dynamic-security/roles.c @@ -204,13 +204,14 @@ 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, *jtmp; + cJSON *j_acl; struct dynsec__acl *acl; - size_t topic_len; - const char *acltype; - const char *topic; cJSON_ArrayForEach(j_acl, j_acls){ + const char *acltype; + const char *topic; + size_t topic_len; + if(json_get_string(j_acl, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS){ continue; } @@ -226,6 +227,11 @@ static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec_ continue; } + HASH_FIND(hh, *acllist, topic, strlen(topic), acl); + if(acl){ + continue; + } + acl = mosquitto_calloc(1, sizeof(struct dynsec__acl) + topic_len + 1); if(acl == NULL){ return 1; @@ -235,9 +241,9 @@ 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); + bool allow; + if(json_get_bool(j_acl, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS){ + acl->allow = allow; } HASH_ADD_INORDER(hh, *acllist, topic, topic_len, acl, insert_acl_cmp); @@ -249,7 +255,7 @@ static int dynsec_roles__acl_load(cJSON *j_acls, const char *key, struct dynsec_ int dynsec_roles__config_load(struct dynsec__data *data, cJSON *tree) { - cJSON *j_roles, *j_role, *jtmp, *j_acls; + cJSON *j_roles, *j_role, *j_acls; struct dynsec__role *role; size_t rolename_len; @@ -305,12 +311,7 @@ int dynsec_roles__config_load(struct dynsec__data *data, cJSON *tree) } /* Allow wildcard subs */ - jtmp = cJSON_GetObjectItem(j_role, "allowwildcardsubs"); - if(jtmp != NULL && cJSON_IsBool(jtmp)){ - role->allow_wildcard_subs = cJSON_IsTrue(jtmp); - }else{ - role->allow_wildcard_subs = true; - } + json_get_bool(j_role, "allowwildcardsubs", &role->allow_wildcard_subs, true, true); /* ACLs */ j_acls = cJSON_GetObjectItem(j_role, "acls"); diff --git a/plugins/examples/deny-protocol-version/mosquitto_deny_protocol_version.c b/plugins/examples/deny-protocol-version/mosquitto_deny_protocol_version.c index 59006d06..d0fb3430 100644 --- a/plugins/examples/deny-protocol-version/mosquitto_deny_protocol_version.c +++ b/plugins/examples/deny-protocol-version/mosquitto_deny_protocol_version.c @@ -107,5 +107,5 @@ int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int op UNUSED(opts); UNUSED(opt_count); - return MOSQ_ERR_SUCCESS; + return mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_BASIC_AUTH, basic_auth_callback, NULL); } diff --git a/plugins/persist-sqlite/restore.c b/plugins/persist-sqlite/restore.c index 3d9cdf72..f4e88bf9 100644 --- a/plugins/persist-sqlite/restore.c +++ b/plugins/persist-sqlite/restore.c @@ -372,7 +372,7 @@ static int client_msg_restore(struct mosquitto_sqlite *ms) client_msg.clientid = (const char *)sqlite3_column_text(stmt, 0); client_msg.cmsg_id = (uint64_t)sqlite3_column_int64(stmt, 1); client_msg.store_id = (uint64_t)sqlite3_column_int64(stmt, 2); - client_msg.dup = sqlite3_column_int(stmt, 3); + client_msg.dup = (uint8_t)sqlite3_column_int(stmt, 3); client_msg.direction = (uint8_t)sqlite3_column_int(stmt, 4); client_msg.mid = (uint16_t)sqlite3_column_int(stmt, 5); client_msg.qos = (uint8_t)sqlite3_column_int(stmt, 6); diff --git a/src/conf.c b/src/conf.c index e288d891..11d391ab 100644 --- a/src/conf.c +++ b/src/conf.c @@ -298,6 +298,7 @@ static void config__init_reload(struct mosquitto__config *config) mosquitto__FREE(config->log_timestamp_format); config->global_max_clients = -1; config->global_max_connections = -1; + config->log_timestamp_format = NULL; config->max_keepalive = 0; config->max_packet_size = 0; config->max_inflight_messages = 20; @@ -1859,6 +1860,7 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload, log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); return MOSQ_ERR_NOMEM; } + cr->log_dest |= MQTT3_LOG_FILE; }else{ log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty \"log_dest file\" value in configuration."); return MOSQ_ERR_INVAL; diff --git a/src/context.c b/src/context.c index 54ce99c5..92ed9720 100644 --- a/src/context.c +++ b/src/context.c @@ -105,8 +105,8 @@ struct mosquitto *context__init(void) context->address = NULL; context->bridge = NULL; context->msgs_in.inflight_maximum = db.config->max_inflight_messages; - context->msgs_out.inflight_maximum = db.config->max_inflight_messages; context->msgs_in.inflight_quota = db.config->max_inflight_messages; + context->msgs_out.inflight_maximum = db.config->max_inflight_messages; context->msgs_out.inflight_quota = db.config->max_inflight_messages; context->max_qos = 2; #ifdef WITH_TLS @@ -116,6 +116,24 @@ struct mosquitto *context__init(void) return context; } +static void context__cleanup_out_packets(struct mosquitto *context) +{ + struct mosquitto__packet *packet; + + if(!context) return; + + while(context->out_packet){ + packet = context->out_packet; + context->out_packet = context->out_packet->next; + mosquitto__free(packet); + } + metrics__int_dec(mosq_gauge_out_packets, context->out_packet_count); + metrics__int_dec(mosq_gauge_out_packet_bytes, context->out_packet_bytes); + context->out_packet_count = 0; + context->out_packet_bytes = 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, @@ -124,8 +142,6 @@ struct mosquitto *context__init(void) */ void context__cleanup(struct mosquitto *context, bool force_free) { - struct mosquitto__packet *packet; - if(!context) return; if(force_free){ @@ -140,6 +156,7 @@ void context__cleanup(struct mosquitto *context, bool force_free) alias__free_all(context); keepalive__remove(context); + context__cleanup_out_packets(context); mosquitto__FREE(context->auth_method); mosquitto__FREE(context->username); @@ -160,15 +177,7 @@ void context__cleanup(struct mosquitto *context, bool force_free) mosquitto__FREE(context->id); } packet__cleanup(&(context->in_packet)); - while(context->out_packet){ - packet = context->out_packet; - context->out_packet = context->out_packet->next; - mosquitto__FREE(packet); - } - metrics__int_dec(mosq_gauge_out_packets, context->out_packet_count); - metrics__int_dec(mosq_gauge_out_packet_bytes, context->out_packet_bytes); - context->out_packet_count = 0; - context->out_packet_bytes = 0; + context__cleanup_out_packets(context); #if defined(WITH_BROKER) && defined(__GLIBC__) && defined(WITH_ADNS) if(context->adns){ gai_cancel(context->adns); diff --git a/src/database.c b/src/database.c index 2a27a8b8..0af3006f 100644 --- a/src/database.c +++ b/src/database.c @@ -964,23 +964,24 @@ int db__message_store(const struct mosquitto *source, struct mosquitto__base_msg return MOSQ_ERR_SUCCESS; } -int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto__base_msg **base_msg) +int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto__client_msg **client_msg) { - struct mosquitto__client_msg *client_msg; + struct mosquitto__client_msg *cmsg; + + *client_msg = NULL; if(!context) return MOSQ_ERR_INVAL; - *base_msg = NULL; - DL_FOREACH(context->msgs_in.inflight, client_msg){ - if(client_msg->base_msg->data.source_mid == mid){ - *base_msg = client_msg->base_msg; + DL_FOREACH(context->msgs_in.inflight, cmsg){ + if(cmsg->base_msg->data.source_mid == mid){ + *client_msg = cmsg; return MOSQ_ERR_SUCCESS; } } - DL_FOREACH(context->msgs_in.queued, client_msg){ - if(client_msg->base_msg->data.source_mid == mid){ - *base_msg = client_msg->base_msg; + DL_FOREACH(context->msgs_in.queued, cmsg){ + if(cmsg->base_msg->data.source_mid == mid){ + *client_msg = cmsg; return MOSQ_ERR_SUCCESS; } } @@ -1084,6 +1085,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. */ + client_msg->data.dup = 0; } } @@ -1094,6 +1096,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context) * will be sent out of order. */ DL_FOREACH_SAFE(context->msgs_in.queued, client_msg, tmp){ + client_msg->data.dup = 0; db__msg_add_to_queued_stats(&context->msgs_in, client_msg); if(db__ready_for_flight(context, mosq_md_in, client_msg->data.qos)){ switch(client_msg->data.qos){ diff --git a/src/handle_connect.c b/src/handle_connect.c index 0605199a..30df1eee 100644 --- a/src/handle_connect.c +++ b/src/handle_connect.c @@ -403,6 +403,10 @@ static int will__read(struct mosquitto *context, const char *clientid, struct mo 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; @@ -1065,6 +1069,7 @@ handle_connect_error: mosquitto__FREE(context->will->msg.payload); mosquitto__FREE(context->will->msg.topic); mosquitto__FREE(context->will); + context->will = NULL; } /* We return an error here which means the client is freed later on. */ context->clean_start = true; diff --git a/src/handle_publish.c b/src/handle_publish.c index efeb9e23..cc56c934 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__base_msg *base_msg, *stored = NULL; + struct mosquitto__client_msg *cmsg_stored = NULL; size_t len; uint16_t slen; char *topic_mount; @@ -300,21 +301,21 @@ int handle__publish(struct mosquitto *context) } if(base_msg->data.qos > 0){ - db__message_store_find(context, base_msg->data.source_mid, &stored); + db__message_store_find(context, base_msg->data.source_mid, &cmsg_stored); } - if(stored && base_msg->data.source_mid != 0 && - (stored->data.qos != base_msg->data.qos - || stored->data.payloadlen != base_msg->data.payloadlen - || strcmp(stored->data.topic, base_msg->data.topic) - || memcmp(stored->data.payload, base_msg->data.payload, base_msg->data.payloadlen) )){ + if(cmsg_stored && cmsg_stored->base_msg && base_msg->data.source_mid != 0 && + (cmsg_stored->base_msg->data.qos != base_msg->data.qos + || cmsg_stored->base_msg->data.payloadlen != base_msg->data.payloadlen + || strcmp(cmsg_stored->base_msg->data.topic, base_msg->data.topic) + || memcmp(cmsg_stored->base_msg->data.payload, base_msg->data.payload, base_msg->data.payloadlen) )){ log__printf(NULL, MOSQ_LOG_WARNING, "Reused message ID %u from %s detected. Clearing from storage.", base_msg->data.source_mid, context->id); db__message_remove_incoming(context, base_msg->data.source_mid); - stored = NULL; + cmsg_stored = NULL; } - if(!stored){ + if(!cmsg_stored){ if(base_msg->data.qos > 0 && context->msgs_in.inflight_quota == 0){ /* Client isn't allowed any more incoming messages, so fail early */ db__msg_store_free(base_msg); @@ -323,7 +324,7 @@ int handle__publish(struct mosquitto *context) if(base_msg->data.qos == 0 || db__ready_for_flight(context, mosq_md_in, base_msg->data.qos) - || db__ready_for_queue(context, base_msg->data.qos, &context->msgs_in)){ + ){ dup = 0; rc = db__message_store(context, base_msg, message_expiry_interval, mosq_mo_client); @@ -335,10 +336,13 @@ int handle__publish(struct mosquitto *context) } stored = base_msg; base_msg = NULL; + dup = 0; }else{ db__msg_store_free(base_msg); base_msg = NULL; - dup = 1; + stored = cmsg_stored->base_msg; + cmsg_stored->data.dup++; + dup = cmsg_stored->data.dup; } switch(stored->data.qos){ @@ -364,11 +368,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->data.source_mid, 0, NULL)) rc = 1; + if(dup == 0 || dup == 1){ + rc2 = send__pubrec(context, stored->data.source_mid, 0, NULL); + if(rc2) rc = rc2; + }else{ + return MOSQ_ERR_PROTOCOL; + } }else if(res == 1){ rc = 1; } @@ -393,5 +403,8 @@ process_bad_message: } db__msg_store_free(base_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 e18a0763..cb9e693a 100644 --- a/src/logging.c +++ b/src/logging.c @@ -21,6 +21,7 @@ Contributors: #include #include #include +#include #ifndef WIN32 #include #endif @@ -135,11 +136,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; diff --git a/src/mosquitto.c b/src/mosquitto.c index 4094cd00..f4e415a2 100644 --- a/src/mosquitto.c +++ b/src/mosquitto.c @@ -239,9 +239,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 @@ -342,7 +351,6 @@ int main(int argc, char *argv[]) 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 377cac42..aeeab72c 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -720,7 +720,7 @@ int db__messages_delete_incoming(struct mosquitto *context); int db__messages_delete_outgoing(struct mosquitto *context); 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__base_msg *base_msg, uint32_t message_expiry_interval, enum mosquitto_msg_origin origin); -int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto__base_msg **base_msg); +int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto__client_msg **client_msg); int db__msg_store_add(struct mosquitto__base_msg *base_msg); void db__msg_store_remove(struct mosquitto__base_msg *base_msg, bool notify); void db__msg_store_ref_inc(struct mosquitto__base_msg *base_msg); diff --git a/src/net.c b/src/net.c index 512beaa0..451d8c33 100644 --- a/src/net.c +++ b/src/net.c @@ -314,6 +314,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); @@ -484,7 +488,7 @@ int net__tls_server_ctx(struct mosquitto__listener *listener) return MOSQ_ERR_TLS; } #else - 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; @@ -548,7 +552,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); @@ -691,6 +695,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; @@ -701,49 +707,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 @@ -825,10 +838,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/persist_read.c b/src/persist_read.c index e3c0e581..0c2182d2 100644 --- a/src/persist_read.c +++ b/src/persist_read.c @@ -424,7 +424,7 @@ int persist__restore(void) subscription_count = 0; client_msg_count = 0; - fptr = mosquitto__fopen(db.config->persistence_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_write.c b/src/persist_write.c index 02125b27..873ee9f4 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->base_msg->data.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->base_msg->data.store_id; chunk.F.mid = cmsg->data.mid; chunk.F.id_len = (uint16_t)strlen(context->id); @@ -91,14 +91,14 @@ static int persist__message_store_save(FILE *db_fptr) assert(db_fptr); - memset(&chunk, 0, sizeof(struct P_base_msg)); - base_msg = db.msg_store; HASH_ITER(hh, db.msg_store, base_msg, base_msg_tmp){ if(base_msg->ref_count < 1 || base_msg->data.topic == NULL){ continue; } + memset(&chunk, 0, sizeof(struct P_base_msg)); + if(!strncmp(base_msg->data.topic, "$SYS", 4)){ if(base_msg->ref_count <= 1 && base_msg->dest_id_count == 0){ /* $SYS messages that are only retained shouldn't be persisted. */ @@ -161,14 +161,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; @@ -218,8 +221,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; @@ -232,6 +233,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); @@ -275,9 +278,9 @@ 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->data.topic, "$SYS", 4)){ + memset(&retain_chunk, 0, sizeof(struct P_retain)); + /* Don't save $SYS messages. */ retain_chunk.F.store_id = node->retained->data.store_id; rc = persist__chunk_retain_write_v6(db_fptr, &retain_chunk); diff --git a/src/plugin_public.c b/src/plugin_public.c index 7fe93b97..4af1271f 100644 --- a/src/plugin_public.c +++ b/src/plugin_public.c @@ -299,6 +299,9 @@ BROKER_EXPORT int mosquitto_set_username(struct mosquitto *client, const char *u 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 d71344a6..3c0fbb33 100644 --- a/src/property_broker.c +++ b/src/property_broker.c @@ -125,6 +125,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/security_default.c b/src/security_default.c index 3c1caf24..ca561583 100644 --- a/src/security_default.c +++ b/src/security_default.c @@ -512,7 +512,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); @@ -737,7 +737,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); diff --git a/src/subs.c b/src/subs.c index 20540e13..c4c8ac8b 100644 --- a/src/subs.c +++ b/src/subs.c @@ -405,8 +405,8 @@ static int sub__remove_shared(struct mosquitto *context, struct mosquitto__subhi * each subleaf. Might be worth considering though. */ for(i=0; isubs_capacity; i++){ if(context->subs[i] == leaf){ - context->subs_count--; mosquitto__FREE(context->subs[i]); + context->subs_count--; break; } } diff --git a/src/sys_tree.c b/src/sys_tree.c index 63e217aa..375d794b 100644 --- a/src/sys_tree.c +++ b/src/sys_tree.c @@ -24,6 +24,7 @@ Contributors: #include #include #include +#include #include "mosquitto_broker_internal.h" #include "memory_mosq.h" 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/06-bridge-reconnect-local-out.py b/test/broker/06-bridge-reconnect-local-out.py index 9c4cfa84..53c24b08 100755 --- a/test/broker/06-bridge-reconnect-local-out.py +++ b/test/broker/06-bridge-reconnect-local-out.py @@ -126,4 +126,3 @@ do_test(proto_ver=4) do_test(proto_ver=5) exit(0) - 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/09-plugin-bad.py b/test/broker/09-plugin-bad.py index 939801aa..65fb9aba 100755 --- a/test/broker/09-plugin-bad.py +++ b/test/broker/09-plugin-bad.py @@ -18,7 +18,7 @@ def do_test(plugver, num): try: rc = 1 broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port, check_port=False) - broker.wait(1) + broker.wait(5) broker.terminate() if broker.returncode == 13: rc = 0 diff --git a/test/broker/09-plugin-unsupported.py b/test/broker/09-plugin-unsupported.py index 0f2a4cc3..bcb8392b 100755 --- a/test/broker/09-plugin-unsupported.py +++ b/test/broker/09-plugin-unsupported.py @@ -18,7 +18,7 @@ def do_test(plugver): try: rc = 1 broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port, check_port=False) - broker.wait(1) + broker.wait(5) broker.terminate() if broker.returncode == 13: rc = 0 diff --git a/test/broker/16-config-parse-errors-without-tls.py b/test/broker/16-config-parse-errors-without-tls.py index 7af9c9dc..62d3a74c 100755 --- a/test/broker/16-config-parse-errors-without-tls.py +++ b/test/broker/16-config-parse-errors-without-tls.py @@ -19,7 +19,7 @@ def do_test(config_str, rc_expected, error_log_entry): try: broker = mosq_test.start_broker(conf_file, check_port=False) - mosq_test.wait_for_subprocess(broker,timeout=1) + mosq_test.wait_for_subprocess(broker,timeout=5) if broker.returncode != rc_expected: (stdo, stde) = broker.communicate() diff --git a/test/broker/Makefile b/test/broker/Makefile index a78dd89d..499ef0fb 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -24,6 +24,7 @@ msg_sequence_test: ./msg_sequence_test.py 01 : + ./01-bad-initial-packets.py ./01-connect-575314.py ./01-connect-accept-protocol.py ./01-connect-allow-anonymous.py @@ -91,6 +92,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-exceeded.py ./03-publish-qos2-max-inflight.py ./03-publish-qos2-reuse-mid.py @@ -137,6 +139,7 @@ msg_sequence_test: ./06-bridge-config-reload.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 2688dc41..314ddde2 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-accept-protocol.py'), (1, './01-connect-allow-anonymous.py'), @@ -70,6 +71,7 @@ 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-exceeded.py'), (1, './03-publish-qos2-max-inflight.py'), (1, './03-publish-qos2-reuse-mid.py'), @@ -112,6 +114,7 @@ tests = [ (2, './06-bridge-remote-shutdown.py'), (2, './06-bridge-config-reload.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 156b0efe..d7173f5e 100644 --- a/test/mosq_test.py +++ b/test/mosq_test.py @@ -122,7 +122,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=None): if cmd is None: diff --git a/test/unit/lib/utf8.c b/test/unit/lib/utf8.c index d7647970..b82078b6 100644 --- a/test/unit/lib/utf8.c +++ b/test/unit/lib/utf8.c @@ -415,10 +415,10 @@ static 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/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/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/ diff --git a/www/posts/2023/08/version-2-0-16-released.md b/www/posts/2023/08/version-2-0-16-released.md new file mode 100644 index 00000000..4cf08a25 --- /dev/null +++ b/www/posts/2023/08/version-2-0-16-released.md @@ -0,0 +1,84 @@ + + +Version 2.0.16 of Mosquitto has been released. This is a security +and bugfix release. + +# 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. + +[CVE-2023-0809]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-0809 +[CVE-2023-28366]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-27366 +[CVE-2023-3592]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3592 +[#2354]: https://github.com/eclipse/mosquitto/issues/2354 +[#2634]: https://github.com/eclipse/mosquitto/issues/2634 +[#2708]: https://github.com/eclipse/mosquitto/issues/2708 +[#2722]: https://github.com/eclipse/mosquitto/issues/2722 +[#2722]: https://github.com/eclipse/mosquitto/issues/2722 +[#2732]: https://github.com/eclipse/mosquitto/issues/2732 +[#2746]: https://github.com/eclipse/mosquitto/issues/2746 +[#2749]: https://github.com/eclipse/mosquitto/issues/2749 +[#2760]: https://github.com/eclipse/mosquitto/issues/2760 +[#2794]: https://github.com/eclipse/mosquitto/issues/2794 +[#1488]: https://github.com/eclipse/mosquitto/issues/1488 + diff --git a/www/posts/2023/08/version-2-0-17-released.md b/www/posts/2023/08/version-2-0-17-released.md new file mode 100644 index 00000000..4dd16a48 --- /dev/null +++ b/www/posts/2023/08/version-2-0-17-released.md @@ -0,0 +1,24 @@ + + +Version 2.0.16 of Mosquitto has been released. This is a bugfix release. + +Broker: +- Fix `max_queued_messages 0` stopping clients from receiving messages. + Closes [#2879]. +- Fix `max_inflight_messages` not being set correctly. Closes [#2876]. + +Apps: +- Fix `mosquitto_passwd -U` backup file creation. Closes [#2873]. + +[#2873]: https://github.com/eclipse/mosquitto/issues/2873 +[#2876]: https://github.com/eclipse/mosquitto/issues/2876 +[#2879]: https://github.com/eclipse/mosquitto/issues/2879