diff --git a/.github/lock.yml b/.github/lock.yml
deleted file mode 100644
index 916ac1f7..00000000
--- a/.github/lock.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-# Configuration for Lock Threads - https://github.com/dessant/lock-threads
-
-# Number of days of inactivity before a closed issue or pull request is locked
-daysUntilLock: 90
-
-# Skip issues and pull requests created before a given timestamp. Timestamp must
-# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
-skipCreatedBefore: false
-
-# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
-exemptLabels: []
-
-# Label to add before locking, such as `outdated`. Set to `false` to disable
-lockLabel: false
-
-# Comment to post before locking. Set to `false` to disable
-lockComment: false
-
-# Assign `resolved` as the reason for locking. Set to `false` to disable
-setLockReason: true
-
-# Limit to only `issues` or `pulls`
-only: issues
-
-# Optionally, specify configuration settings just for `issues` or `pulls`
-# issues:
-# exemptLabels:
-# - help-wanted
-# lockLabel: outdated
-
-# pulls:
-# daysUntilLock: 30
-
-# Repository to extend settings from
-# _extends: repo
diff --git a/.github/workflows/coverity-scan-develop.yml b/.github/workflows/coverity-scan-develop.yml
new file mode 100644
index 00000000..0ef60c83
--- /dev/null
+++ b/.github/workflows/coverity-scan-develop.yml
@@ -0,0 +1,26 @@
+name: Coverity Scan develop branch on a weekly basis
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "7 3 * * 0"
+
+jobs:
+ coverity:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: develop
+
+ - name: Dependencies
+ run: sudo apt-get install -y libcjson-dev libsqlite3-dev libssl-dev uthash-dev
+
+ - uses: vapier/coverity-scan-action@v1
+ with:
+ build_language: 'cxx'
+ project: "eclipse/mosquitto"
+ token: ${{ secrets.COVERITY_SCAN_TOKEN }}
+ email: ${{ secrets.COVERITY_SCAN_EMAIL }}
+ command: "make binary"
+
diff --git a/.github/workflows/coverity-scan-fixes.yml b/.github/workflows/coverity-scan-fixes.yml
new file mode 100644
index 00000000..2f11d013
--- /dev/null
+++ b/.github/workflows/coverity-scan-fixes.yml
@@ -0,0 +1,26 @@
+name: Coverity Scan fixes branch on a weekly basis
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "7 3 * * 3"
+
+jobs:
+ coverity:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: fixes
+
+ - name: Dependencies
+ run: sudo apt-get install -y libcjson-dev libsqlite3-dev libssl-dev uthash-dev
+
+ - uses: vapier/coverity-scan-action@v1
+ with:
+ build_language: 'cxx'
+ project: "eclipse/mosquitto"
+ token: ${{ secrets.COVERITY_SCAN_TOKEN }}
+ email: ${{ secrets.COVERITY_SCAN_EMAIL }}
+ command: "make binary"
+
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 00000000..920a0259
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,21 @@
+name: 'Lock Threads'
+
+on:
+ schedule:
+ - cron: '0 0 * * 0'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+concurrency:
+ group: lock
+
+jobs:
+ action:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v3
+ with:
+ issue-inactive-days: '90'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e38a1a3..07b24642 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,7 @@ project(mosquitto
LANGUAGES C CXX
)
-list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/")
+list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
add_definitions (-DCMAKE -DVERSION=\"${VERSION}\")
diff --git a/ChangeLog.txt b/ChangeLog.txt
index a69645ae..6ce25841 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -161,6 +161,73 @@ Build:
- Add an CMake option `WITH_LTO` to enable/disable link time optimization.
+2.0.15 - 2022-08-16
+===================
+
+Security:
+- Deleting the group configured as the anonymous group in the Dynamic Security
+ plugin, would leave a dangling pointer that could lead to a single crash.
+ This is considered a minor issue - only administrative users should have
+ access to dynsec, the impact on availability is one-off, and there is no
+ associated loss of data. It is now forbidden to delete the group configured
+ as the anonymous group.
+
+Broker:
+- Fix memory leak when a plugin modifies the topic of a message in
+ MOSQ_EVT_MESSAGE.
+- Fix bridge `restart_timeout` not being honoured.
+- Fix potential memory leaks if a plugin modifies the message in the
+ MOSQ_EVT_MESSAGE event.
+- Fix unused flags in CONNECT command being forced to be 0, which is not
+ required for MQTT v3.1. Closes #2522.
+- Improve documentation of `persistent_client_expiration` option.
+ Closes #2404.
+- Add clients to session expiry check list when restarting and reloading from
+ persistence. Closes #2546.
+- Fix bridges not sending failure notification messages to the local broker if
+ the remote bridge connection fails. Closes #2467. Closes #1488.
+- Fix some PUBLISH messages not being counted in $SYS stats. Closes #2448.
+- Fix incorrect return code being sent in DISCONNECT when a client session is
+ taken over. Closes #2607.
+- Fix confusing "out of memory" error when a client is kicked in the dynamic
+ security plugin. Closes #2525.
+- Fix confusing error message when dynamic security config file was a
+ directory. Closes #2520.
+- Fix bridge queued messages not being persisted when local_cleansession is
+ set to false and cleansession is set to true. Closes #2604.
+- Dynamic security: Fix modifyClient and modifyGroup commands to not modify
+ the client/group if a new group/client being added is not valid.
+ Closes #2598.
+- Dynamic security: Fix the plugin being able to be loaded twice. Currently
+ only a single plugin can interact with a unique $CONTROL topic. Using
+ multiple instances of the plugin would produce duplicate entries in the
+ config file. Closes #2601. Closes #2470.
+- Fix case where expired messages were causing queued messages not to be
+ delivered. Closes #2609.
+- Fix websockets not passing on the X-Forwarded-For header.
+
+Client library:
+- Fix threads library detection on Windows under cmake. Bumps the minimum
+ cmake version to 3.1, which is still ancient.
+- Fix use of `MOSQ_OPT_TLS_ENGINE` being unable to be used due to the openssl
+ ctx not being initialised until starting to connect. Closes #2537.
+- Fix incorrect use of SSL_connect. Closes #2594.
+- Don't set SIGPIPE to ignore, use MSG_NOSIGNAL instead. Closes #2564.
+- Add documentation of struct mosquitto_message to header. Closes #2561.
+- Fix documentation omission around mosquitto_reinitialise. Closes #2489.
+- Fix use of MOSQ_OPT_SSL_CTX when used in conjunction with
+ MOSQ_OPT_SSL_CTX_DEFAULTS. Closes #2463.
+- Fix failure to close thread in some situations. Closes #2545.
+
+Clients:
+- Fix mosquitto_pub incorrectly reusing topic aliases when reconnecting.
+ Closes #2494.
+
+Apps:
+- Fix `-o` not working in `mosquitto_ctrl`, and typo in related documentation.
+ Closes #2471.
+
+
2.0.14 - 2021-11-17
===================
@@ -188,6 +255,9 @@ Broker:
- Fix broker sending duplicate CONNACK on failed MQTT v5 reauthentication.
Closes #2339.
- Fix mosquitto_plugin.h not including mosquitto_broker.h. Closes #2350.
+- Fix unlimited message quota not being properly checked for incoming
+ messages. Closes #2593.
+- Fixed build for openssl compiled with OPENSSL_NO_ENGINE. Closes #2589.
Client library:
- Initialise sockpairR/W to invalid in `mosquitto_reinitialise()` to avoid
diff --git a/apps/db_dump/stubs.c b/apps/db_dump/stubs.c
index 93273e1b..d9b5592d 100644
--- a/apps/db_dump/stubs.c
+++ b/apps/db_dump/stubs.c
@@ -152,3 +152,10 @@ void db__msg_add_to_queued_stats(struct mosquitto_msg_data *msg_data, struct mos
UNUSED(msg_data);
UNUSED(msg);
}
+
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
+{
+ UNUSED(context);
+ UNUSED(expiry_time);
+ return 0;
+}
diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c
index 5c6854a7..df408bd4 100644
--- a/apps/mosquitto_ctrl/dynsec.c
+++ b/apps/mosquitto_ctrl/dynsec.c
@@ -22,6 +22,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "mosquitto_ctrl.h"
#include "mosquitto.h"
#include "base64_mosq.h"
diff --git a/apps/mosquitto_ctrl/dynsec_role.c b/apps/mosquitto_ctrl/dynsec_role.c
index 34a13d80..cae3a6fb 100644
--- a/apps/mosquitto_ctrl/dynsec_role.c
+++ b/apps/mosquitto_ctrl/dynsec_role.c
@@ -22,6 +22,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
#include "password_mosq.h"
diff --git a/apps/mosquitto_ctrl/example.c b/apps/mosquitto_ctrl/example.c
index 7693f296..67f0272f 100644
--- a/apps/mosquitto_ctrl/example.c
+++ b/apps/mosquitto_ctrl/example.c
@@ -22,6 +22,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "mosquitto_ctrl.h"
void ctrl_help(void)
diff --git a/apps/mosquitto_ctrl/mosquitto_ctrl.c b/apps/mosquitto_ctrl/mosquitto_ctrl.c
index fbd90298..d998859b 100644
--- a/apps/mosquitto_ctrl/mosquitto_ctrl.c
+++ b/apps/mosquitto_ctrl/mosquitto_ctrl.c
@@ -24,6 +24,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "lib_load.h"
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
diff --git a/apps/mosquitto_ctrl/options.c b/apps/mosquitto_ctrl/options.c
index b53dc580..f606089a 100644
--- a/apps/mosquitto_ctrl/options.c
+++ b/apps/mosquitto_ctrl/options.c
@@ -90,13 +90,14 @@ int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[])
init_config(cfg);
- rc = client_config_load(cfg);
- if(rc) return rc;
-
/* Deal with real argc/argv */
rc = client_config_line_proc(cfg, argc, argv);
if(rc) return rc;
+ /* Load options from config file - this must be after `-o` has been processed */
+ rc = client_config_load(cfg);
+ if(rc) return rc;
+
#ifdef WITH_TLS
if((cfg->certfile && !cfg->keyfile) || (cfg->keyfile && !cfg->certfile)){
fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is set.\n");
@@ -541,7 +542,7 @@ int client_config_load(struct mosq_config *cfg)
fclose(fptr);
return 1;
}
- while(fgets(line, 1024, fptr)){
+ while(fgets(line, sizeof(line), fptr)){
if(line[0] == '#') continue; /* Comments */
while(line[strlen(line)-1] == 10 || line[strlen(line)-1] == 13){
diff --git a/client/pub_client.c b/client/pub_client.c
index de0a1fc4..d8125d68 100644
--- a/client/pub_client.c
+++ b/client/pub_client.c
@@ -135,6 +135,7 @@ void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flag
connack_result = result;
if(!result){
+ first_publish = true;
switch(cfg.pub_mode){
case MSGMODE_CMD:
case MSGMODE_FILE:
diff --git a/config.mk b/config.mk
index 345474ae..fd83cfe0 100644
--- a/config.mk
+++ b/config.mk
@@ -272,6 +272,7 @@ ifeq ($(WITH_THREADING),yes)
BROKER_CFLAGS:=$(BROKER_CFLAGS) -pthread
BROKER_LDFLAGS:=$(BROKER_LDFLAGS) -pthread
LIB_CFLAGS:=$(LIB_CFLAGS) -pthread
+ LIB_LDFLAGS:=$(LIB_LDFLAGS) -pthread
LIB_CPPFLAGS:=$(LIB_CPPFLAGS) -DWITH_THREADING
LIB_LDFLAGS:=$(LIB_LDFLAGS) -pthread
CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -pthread
diff --git a/docker/1.5-openssl/Dockerfile b/docker/1.5-openssl/Dockerfile
index c4e0a3f7..2d258cbd 100644
--- a/docker/1.5-openssl/Dockerfile
+++ b/docker/1.5-openssl/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.12
+FROM alpine:3.14
LABEL maintainer="Roger Light " \
description="Eclipse Mosquitto MQTT Broker"
@@ -40,9 +40,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ htps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/docker/1.5/Dockerfile b/docker/1.5/Dockerfile
index 00cfd691..45c3c162 100644
--- a/docker/1.5/Dockerfile
+++ b/docker/1.5/Dockerfile
@@ -42,9 +42,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
@@ -65,7 +64,7 @@ RUN set -x && \
WITH_SHARED_LIBRARIES=yes \
WITH_SRV=no \
WITH_STRIP=yes \
- WITH_TLS_PSK=no \
+ WITH_TLS_PSK=no \
WITH_WEBSOCKETS=yes \
prefix=/usr \
binary && \
@@ -82,8 +81,8 @@ RUN set -x && \
chown -R mosquitto:mosquitto /mosquitto && \
apk --no-cache add \
ca-certificates \
- libressl \
- libuuid && \
+ libressl \
+ libuuid && \
apk del build-deps && \
rm -rf /build
diff --git a/docker/1.6-openssl/Dockerfile b/docker/1.6-openssl/Dockerfile
index 13769d51..025d85f1 100644
--- a/docker/1.6-openssl/Dockerfile
+++ b/docker/1.6-openssl/Dockerfile
@@ -45,9 +45,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/docker/1.6/Dockerfile b/docker/1.6/Dockerfile
index 882da193..75b8cf42 100644
--- a/docker/1.6/Dockerfile
+++ b/docker/1.6/Dockerfile
@@ -45,9 +45,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/docker/2.0-openssl/Dockerfile b/docker/2.0-openssl/Dockerfile
index 44f09b2d..d0eb1ebb 100644
--- a/docker/2.0-openssl/Dockerfile
+++ b/docker/2.0-openssl/Dockerfile
@@ -1,10 +1,10 @@
-FROM alpine:3.14
+FROM alpine:3.16
LABEL maintainer="Roger Light " \
description="Eclipse Mosquitto MQTT Broker"
-ENV VERSION=2.0.14 \
- DOWNLOAD_SHA256=d0dde8fdb12caf6e2426b4f28081919a2fce3448773bdb8af0d3cd5fe5776925 \
+ENV VERSION=2.0.15 \
+ DOWNLOAD_SHA256=4735b1d32e3f91c7a8896741d88a3022e89730a1ee897946decfa0df27039ac6 \
GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 \
LWS_VERSION=4.2.1 \
LWS_SHA256=842da21f73ccba2be59e680de10a8cce7928313048750eb6ad73b6fa50763c51
@@ -46,9 +46,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/docker/2.0/Dockerfile b/docker/2.0/Dockerfile
index 3df68360..a1056a4c 100644
--- a/docker/2.0/Dockerfile
+++ b/docker/2.0/Dockerfile
@@ -1,10 +1,10 @@
-FROM alpine:3.14
+FROM alpine:3.16
LABEL maintainer="Roger Light " \
description="Eclipse Mosquitto MQTT Broker"
-ENV VERSION=2.0.14 \
- DOWNLOAD_SHA256=d0dde8fdb12caf6e2426b4f28081919a2fce3448773bdb8af0d3cd5fe5776925 \
+ENV VERSION=2.0.15 \
+ DOWNLOAD_SHA256=4735b1d32e3f91c7a8896741d88a3022e89730a1ee897946decfa0df27039ac6 \
GPG_KEYS=A0D6EEA1DCAE49A635A3B2F0779B22DFB3E717B7 \
LWS_VERSION=4.2.1 \
LWS_SHA256=842da21f73ccba2be59e680de10a8cce7928313048750eb6ad73b6fa50763c51
@@ -46,9 +46,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/docker/generic/Dockerfile b/docker/generic/Dockerfile
index a855bd7c..656f8a15 100644
--- a/docker/generic/Dockerfile
+++ b/docker/generic/Dockerfile
@@ -63,9 +63,8 @@ RUN set -x && \
export GNUPGHOME="$(mktemp -d)" && \
found=''; \
for server in \
- ha.pool.sks-keyservers.net \
+ hkps://keys.openpgp.org \
hkp://keyserver.ubuntu.com:80 \
- hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $GPG_KEYS from $server"; \
diff --git a/include/mosquitto.h b/include/mosquitto.h
index 161cb7e3..bd6f1af7 100644
--- a/include/mosquitto.h
+++ b/include/mosquitto.h
@@ -85,7 +85,8 @@ extern "C" {
#define MOSQ_LOG_INTERNAL 0x80000000U
#define MOSQ_LOG_ALL 0xFFFFFFFFU
-/* Error values */
+/* Enum: mosq_err_t
+ * Integer values returned from many libmosquitto functions. */
enum mosq_err_t {
MOSQ_ERR_QUOTA_EXCEEDED = -6,
MOSQ_ERR_AUTH_DELAYED = -5,
@@ -142,7 +143,12 @@ enum mosq_err_t {
MOSQ_ERR_CONNECTION_RATE_EXCEEDED = 159,
};
-/* Option values */
+/* Enum: mosq_opt_t
+ *
+ * Client options.
+ *
+ * See , , and .
+ */
enum mosq_opt_t {
MOSQ_OPT_PROTOCOL_VERSION = 1,
MOSQ_OPT_SSL_CTX = 2,
@@ -175,6 +181,24 @@ enum mosq_transport_t {
#define MQTT_PROTOCOL_V311 4
#define MQTT_PROTOCOL_V5 5
+/* Struct: mosquitto_message
+ *
+ * Contains details of a PUBLISH message.
+ *
+ * int mid - the message/packet ID of the PUBLISH message, assuming this is a
+ * QoS 1 or 2 message. Will be set to 0 for QoS 0 messages.
+ *
+ * char *topic - the topic the message was delivered on.
+ *
+ * void *payload - the message payload. This will be payloadlen bytes long, and
+ * may be NULL if a zero length payload was sent.
+ *
+ * int payloadlen - the length of the payload, in bytes.
+ *
+ * int qos - the quality of service of the message, 0, 1, or 2.
+ *
+ * bool retain - set to true for stale retained messages.
+ */
struct mosquitto_message{
int mid;
char *topic;
@@ -359,9 +383,10 @@ libmosq_EXPORT void mosquitto_destroy(struct mosquitto *mosq);
* callbacks that are specified.
*
* Returns:
- * MOSQ_ERR_SUCCESS - on success.
- * MOSQ_ERR_INVAL - if the input parameters were invalid.
- * MOSQ_ERR_NOMEM - if an out of memory condition occurred.
+ * MOSQ_ERR_SUCCESS - on success.
+ * MOSQ_ERR_INVAL - if the input parameters were invalid.
+ * MOSQ_ERR_NOMEM - if an out of memory condition occurred.
+ * MOSQ_ERR_MALFORMED_UTF8 - if the client id is not valid UTF-8.
*
* See Also:
* ,
@@ -1623,6 +1648,9 @@ libmosq_EXPORT int mosquitto_int_option(struct mosquitto *mosq, enum mosq_opt_t
* MOSQ_OPT_TLS_ENGINE - Configure the client for TLS Engine support.
* Pass a TLS Engine ID to be used when creating TLS
* connections. Must be set before .
+ * Must be a valid engine, and note that the string will not be used
+ * until a connection attempt is made so this function will return
+ * success even if an invalid engine string is passed.
*
* MOSQ_OPT_TLS_KEYFORM - Configure the client to treat the keyfile
* differently depending on its type. Must be set
diff --git a/lib/connect.c b/lib/connect.c
index 58e7c53c..ba011006 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -78,6 +78,7 @@ static int mosquitto__connect_init(struct mosquitto *mosq, const char *host, int
mosq->msgs_in.inflight_quota = mosq->msgs_in.inflight_maximum;
mosq->msgs_out.inflight_quota = mosq->msgs_out.inflight_maximum;
mosq->retain_available = 1;
+ mosquitto__set_request_disconnect(mosq, false);
return MOSQ_ERR_SUCCESS;
}
@@ -265,6 +266,7 @@ int mosquitto_disconnect_v5(struct mosquitto *mosq, int reason_code, const mosqu
}
mosquitto__set_state(mosq, mosq_cs_disconnected);
+ mosquitto__set_request_disconnect(mosq, true);
if(!net__is_connected(mosq)){
return MOSQ_ERR_NO_CONN;
}else{
diff --git a/lib/loop.c b/lib/loop.c
index 02a22e2d..6be75ee7 100644
--- a/lib/loop.c
+++ b/lib/loop.c
@@ -108,9 +108,11 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets)
}
now = mosquitto_time();
+ pthread_mutex_lock(&mosq->msgtime_mutex);
if(mosq->next_msg_out && now + timeout_ms/1000 > mosq->next_msg_out){
timeout_ms = (mosq->next_msg_out - now)*1000;
}
+ pthread_mutex_unlock(&mosq->msgtime_mutex);
if(timeout_ms < 0){
/* There has been a delay somewhere which means we should have already
@@ -161,7 +163,6 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets)
FD_SET(mosq->sock, &writefds);
}
}
-
if(net__is_connected(mosq) && FD_ISSET(mosq->sock, &writefds)){
rc = mosquitto_loop_write(mosq, max_packets);
if(rc || !net__is_connected(mosq)){
@@ -242,7 +243,6 @@ int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets)
int run = 1;
int rc = MOSQ_ERR_SUCCESS;
unsigned long reconnect_delay;
- enum mosquitto_client_state state;
if(!mosq) return MOSQ_ERR_INVAL;
@@ -281,8 +281,7 @@ int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets)
pthread_testcancel();
#endif
rc = MOSQ_ERR_SUCCESS;
- state = mosquitto__get_state(mosq);
- if(state == mosq_cs_disconnecting || state == mosq_cs_disconnected){
+ if(mosquitto__get_request_disconnect(mosq)){
run = 0;
}else{
if(mosq->reconnect_delay_max > mosq->reconnect_delay){
@@ -304,8 +303,7 @@ int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets)
rc = interruptible_sleep(mosq, (time_t)reconnect_delay);
if(rc) return rc;
- state = mosquitto__get_state(mosq);
- if(state == mosq_cs_disconnecting || state == mosq_cs_disconnected){
+ if(mosquitto__get_request_disconnect(mosq)){
run = 0;
}else{
rc = mosquitto_reconnect(mosq);
diff --git a/lib/mosquitto.c b/lib/mosquitto.c
index 1fecc3c1..60f0d2c0 100644
--- a/lib/mosquitto.c
+++ b/lib/mosquitto.c
@@ -109,10 +109,6 @@ struct mosquitto *mosquitto_new(const char *id, bool clean_start, void *userdata
return NULL;
}
-#ifndef WIN32
- signal(SIGPIPE, SIG_IGN);
-#endif
-
mosq = (struct mosquitto *)mosquitto__calloc(1, sizeof(struct mosquitto));
if(mosq){
mosq->sock = INVALID_SOCKET;
@@ -179,6 +175,9 @@ int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_st
return MOSQ_ERR_MALFORMED_UTF8;
}
mosq->id = mosquitto__strdup(id);
+ if(!mosq->id){
+ return MOSQ_ERR_NOMEM;
+ }
}
packet__cleanup(&mosq->in_packet);
mosq->out_packet = NULL;
diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h
index 56893ab3..eae0789c 100644
--- a/lib/mosquitto_internal.h
+++ b/lib/mosquitto_internal.h
@@ -404,8 +404,9 @@ struct mosquitto {
unsigned int reconnect_delay_max;
int callback_depth;
uint16_t port;
- bool reconnect_exponential_backoff;
bool disable_socketpair;
+ bool reconnect_exponential_backoff;
+ bool request_disconnect;
char threaded;
struct mosquitto__packet *out_packet_last;
mosquitto_property *connect_properties;
diff --git a/lib/net_mosq.c b/lib/net_mosq.c
index 41c5f06e..894d1d14 100644
--- a/lib/net_mosq.c
+++ b/lib/net_mosq.c
@@ -674,8 +674,8 @@ static int net__init_ssl_ctx(struct mosquitto *mosq)
* has not been set, or if both of MOSQ_OPT_SSL_CTX and
* MOSQ_OPT_SSL_CTX_WITH_DEFAULTS are set. */
if(mosq->tls_cafile || mosq->tls_capath || mosq->tls_psk || mosq->tls_use_os_certs){
+ net__init_tls();
if(!mosq->ssl_ctx){
- net__init_tls();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
mosq->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
@@ -1042,11 +1042,7 @@ ssize_t net__write(struct mosquitto *mosq, const void *buf, size_t count)
/* Call normal write/send */
#endif
-#ifndef WIN32
- return write(mosq->sock, buf, count);
-#else
- return send(mosq->sock, buf, count, 0);
-#endif
+ return send(mosq->sock, buf, count, MSG_NOSIGNAL);
#ifdef WITH_TLS
}
diff --git a/lib/net_mosq.h b/lib/net_mosq.h
index d5bb45dc..54e4f982 100644
--- a/lib/net_mosq.h
+++ b/lib/net_mosq.h
@@ -19,6 +19,7 @@ Contributors:
#define NET_MOSQ_H
#ifndef WIN32
+# include
# include
#else
# include
@@ -51,6 +52,10 @@ typedef SSIZE_T ssize_t;
#define INVALID_SOCKET -1
#endif
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0
+#endif
+
/* Macros for accessing the MSB and LSB of a uint16_t */
#define MOSQ_MSB(A) (uint8_t)((A & 0xFF00) >> 8)
#define MOSQ_LSB(A) (uint8_t)(A & 0x00FF)
diff --git a/lib/options.c b/lib/options.c
index f6fdd77b..8f33f675 100644
--- a/lib/options.c
+++ b/lib/options.c
@@ -274,14 +274,17 @@ int mosquitto_string_option(struct mosquitto *mosq, enum mosq_opt_t option, cons
switch(option){
case MOSQ_OPT_TLS_ENGINE:
#if defined(WITH_TLS) && !defined(OPENSSL_NO_ENGINE)
- eng = ENGINE_by_id(value);
- if(!eng){
- return MOSQ_ERR_INVAL;
- }
- ENGINE_free(eng); /* release the structural reference from ENGINE_by_id() */
- mosq->tls_engine = mosquitto__strdup(value);
- if(!mosq->tls_engine){
- return MOSQ_ERR_NOMEM;
+ mosquitto__free(mosq->tls_engine);
+ if(value){
+ eng = ENGINE_by_id(value);
+ if(!eng){
+ return MOSQ_ERR_INVAL;
+ }
+ ENGINE_free(eng); /* release the structural reference from ENGINE_by_id() */
+ mosq->tls_engine = mosquitto__strdup(value);
+ if(!mosq->tls_engine){
+ return MOSQ_ERR_NOMEM;
+ }
}
return MOSQ_ERR_SUCCESS;
#else
diff --git a/lib/packet_mosq.c b/lib/packet_mosq.c
index d5ef8792..fa044941 100644
--- a/lib/packet_mosq.c
+++ b/lib/packet_mosq.c
@@ -558,7 +558,7 @@ int packet__read(struct mosquitto *mosq)
mosq->in_packet.pos = 0;
#ifdef WITH_BROKER
G_MSGS_RECEIVED_INC(1);
- if(((mosq->in_packet.command)&0xF5) == CMD_PUBLISH){
+ if(((mosq->in_packet.command)&0xF0) == CMD_PUBLISH){
G_PUB_MSGS_RECEIVED_INC(1);
}
#endif
diff --git a/lib/property_mosq.c b/lib/property_mosq.c
index 64763487..ba319427 100644
--- a/lib/property_mosq.c
+++ b/lib/property_mosq.c
@@ -1206,6 +1206,7 @@ BROKER_EXPORT int mosquitto_property_copy_all(mosquitto_property **dest, const m
}
plast = pnew;
+ pnew->client_generated = src->client_generated;
pnew->identifier = src->identifier;
switch(pnew->identifier){
case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR:
diff --git a/lib/strings_mosq.c b/lib/strings_mosq.c
index 6fd167ed..850ed9b5 100644
--- a/lib/strings_mosq.c
+++ b/lib/strings_mosq.c
@@ -21,6 +21,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "mosquitto.h"
#include "mqtt_protocol.h"
diff --git a/lib/util_mosq.c b/lib/util_mosq.c
index 86fcfca3..3d846ae7 100644
--- a/lib/util_mosq.c
+++ b/lib/util_mosq.c
@@ -106,6 +106,11 @@ int mosquitto__check_keepalive(struct mosquitto *mosq)
pthread_mutex_unlock(&mosq->msgtime_mutex);
}else{
#ifdef WITH_BROKER
+# ifdef WITH_BRIDGE
+ if(mosq->bridge){
+ context__send_will(mosq);
+ }
+# endif
net__socket_close(mosq);
#else
net__socket_close(mosq);
@@ -288,3 +293,23 @@ enum mosquitto_client_state mosquitto__get_state(struct mosquitto *mosq)
return state;
}
+
+#ifndef WITH_BROKER
+void mosquitto__set_request_disconnect(struct mosquitto *mosq, bool request_disconnect)
+{
+ pthread_mutex_lock(&mosq->state_mutex);
+ mosq->request_disconnect = request_disconnect;
+ pthread_mutex_unlock(&mosq->state_mutex);
+}
+
+bool mosquitto__get_request_disconnect(struct mosquitto *mosq)
+{
+ bool request_disconnect;
+
+ pthread_mutex_lock(&mosq->state_mutex);
+ request_disconnect = mosq->request_disconnect;
+ pthread_mutex_unlock(&mosq->state_mutex);
+
+ return request_disconnect;
+}
+#endif
diff --git a/lib/util_mosq.h b/lib/util_mosq.h
index ff1e1a46..ebd6881d 100644
--- a/lib/util_mosq.h
+++ b/lib/util_mosq.h
@@ -32,6 +32,10 @@ uint16_t mosquitto__mid_generate(struct mosquitto *mosq);
int mosquitto__set_state(struct mosquitto *mosq, enum mosquitto_client_state state);
enum mosquitto_client_state mosquitto__get_state(struct mosquitto *mosq);
+#ifndef WITH_BROKER
+void mosquitto__set_request_disconnect(struct mosquitto *mosq, bool request_disconnect);
+bool mosquitto__get_request_disconnect(struct mosquitto *mosq);
+#endif
#ifdef WITH_TLS
int mosquitto__hex2bin_sha1(const char *hex, unsigned char **bin);
diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt
index e6d6545f..93b8b190 100644
--- a/man/CMakeLists.txt
+++ b/man/CMakeLists.txt
@@ -8,10 +8,10 @@ if(NOT WIN32)
find_program(XSLTPROC xsltproc OPTIONAL)
if(XSLTPROC)
function(compile_manpage page)
- add_custom_command(OUTPUT "${CMAKE_SOURCE_DIR}/man/${page}"
- COMMAND xsltproc "${CMAKE_SOURCE_DIR}/man/${page}.xml" -o "${CMAKE_SOURCE_DIR}/man/"
- MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/man/${page}.xml")
- add_custom_target(${page} ALL DEPENDS "${CMAKE_SOURCE_DIR}/man/${page}")
+ add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/man/${page}
+ COMMAND xsltproc ${PROJECT_SOURCE_DIR}/man/${page}.xml -o ${PROJECT_SOURCE_DIR}/man/
+ MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/man/${page}.xml)
+ add_custom_target(${page} ALL DEPENDS ${PROJECT_SOURCE_DIR}/man/${page})
endfunction()
compile_manpage("libmosquitto.3")
diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml
index 9878e8a6..0296b77a 100644
--- a/man/mosquitto.conf.5.xml
+++ b/man/mosquitto.conf.5.xml
@@ -925,15 +925,21 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S
persistent_client_expiration duration
- This option allows persistent clients (those with
- clean session set to false) to be removed if they do
- not reconnect within a certain time frame. This is a
- non-standard option. As far as the MQTT spec is
- concerned, persistent clients persist forever.
- Badly designed clients may set clean session to false
- whilst using a randomly generated client id. This leads
- to persistent clients that will never reconnect. This
- option allows these clients to be removed.
+
+ This option allows the session of persistent clients (those with clean
+ session set to false) that are not currently connected to be removed if they
+ do not reconnect within a certain time frame. This is a non-standard option
+ in MQTT v3.1. MQTT v3.1.1 and v5.0 allow brokers to remove client sessions.
+
+
+
+ Badly designed clients may set clean session to false whilst using a randomly
+ generated client id. This leads to persistent clients that connect once and
+ never reconnect. This option allows these clients to be removed. This option
+ allows persistent clients (those with clean session set to false) to be
+ removed if they do not reconnect within a certain time frame.
+
+
The expiration period should be an integer followed
by one of h d w m y for hour, day, week, month and year
respectively. For example:
@@ -1043,7 +1049,9 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S
Set to true to queue
messages with QoS 0 when a persistent client is
- disconnected. These messages are included in the limit
+ disconnected. When bridges topics are configured with QoS level 1 or 2 incoming
+ QoS 0 messages for these topics are also queued.
+ These messages are included in the limit
imposed by max_queued_messages. Defaults to
false .
Note that the MQTT v3.1.1 spec states that only QoS 1
@@ -1398,6 +1406,7 @@ accept_protocol_versions 3, 4
disconnected as not authorised when this option is
set to true. Do not use in conjunction with
clientid_prefixes .
+ This does not apply globally, but on a per-listener basis.
See also
use_identity_as_username .
Not reloaded on reload signal.
diff --git a/mosquitto.conf b/mosquitto.conf
index 49e2265e..810be29f 100644
--- a/mosquitto.conf
+++ b/mosquitto.conf
@@ -160,14 +160,16 @@
# accepted. MQTT imposes a maximum payload size of 268435455 bytes.
#message_size_limit 0
-# This option allows persistent clients (those with clean session set to false)
-# to be removed if they do not reconnect within a certain time frame.
-#
-# This is a non-standard option in MQTT V3.1 but allowed in MQTT v3.1.1.
+# This option allows the session of persistent clients (those with clean
+# session set to false) that are not currently connected to be removed if they
+# do not reconnect within a certain time frame. This is a non-standard option
+# in MQTT v3.1. MQTT v3.1.1 and v5.0 allow brokers to remove client sessions.
#
# Badly designed clients may set clean session to false whilst using a randomly
-# generated client id. This leads to persistent clients that will never
-# reconnect. This option allows these clients to be removed.
+# generated client id. This leads to persistent clients that connect once and
+# never reconnect. This option allows these clients to be removed. This option
+# allows persistent clients (those with clean session set to false) to be
+# removed if they do not reconnect within a certain time frame.
#
# The expiration period should be an integer followed by one of h d w m y for
# hour, day, week, month and year respectively. For example
@@ -324,6 +326,7 @@
# authorised when this option is set to true.
# Do not use in conjunction with clientid_prefixes.
# See also use_identity_as_username.
+# This does not apply globally, but on a per-listener basis.
#use_username_as_clientid
# Change the websockets headers size. This is a global option, it is not
diff --git a/plugins/deny-protocol-version/CMakeLists.txt b/plugins/deny-protocol-version/CMakeLists.txt
new file mode 100644
index 00000000..d998df75
--- /dev/null
+++ b/plugins/deny-protocol-version/CMakeLists.txt
@@ -0,0 +1,24 @@
+set (PLUGIN_NAME mosquitto_deny_protocol_version)
+
+add_library(${PLUGIN_NAME} MODULE
+ ${PLUGIN_NAME}.c
+)
+
+target_include_directories(${PLUGIN_NAME} PRIVATE
+ "${OPENSSL_INCLUDE_DIR}"
+ "${STDBOOL_H_PATH} ${STDINT_H_PATH}"
+ "${mosquitto_SOURCE_DIR}"
+ "${mosquitto_SOURCE_DIR}/include"
+)
+
+set_target_properties(${PLUGIN_NAME} PROPERTIES
+ PREFIX ""
+ POSITION_INDEPENDENT_CODE 1
+)
+
+if(WIN32)
+ target_link_libraries(${PLUGIN_NAME} PRIVATE mosquitto)
+endif()
+
+# Don't install, these are example plugins only.
+#install(TARGETS ${PLUGIN_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
diff --git a/plugins/deny-protocol-version/Makefile b/plugins/deny-protocol-version/Makefile
new file mode 100644
index 00000000..42e6c806
--- /dev/null
+++ b/plugins/deny-protocol-version/Makefile
@@ -0,0 +1,29 @@
+R=../..
+include ${R}/config.mk
+
+.PHONY : all binary check clean reallyclean test install uninstall
+
+PLUGIN_NAME=mosquitto_deny_protocol_version
+PLUGIN_CFLAGS+=-I${R}/include -I${R}/
+
+all : binary
+
+binary : ${PLUGIN_NAME}.so
+
+${PLUGIN_NAME}.so : ${PLUGIN_NAME}.c
+ $(CROSS_COMPILE)$(CC) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) $(PLUGIN_LDFLAGS) -fPIC -shared $< -o $@
+
+reallyclean : clean
+clean:
+ -rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno
+
+check: test
+test:
+
+install: ${PLUGIN_NAME}.so
+ # Don't install, these are examples only.
+ #$(INSTALL) -d "${DESTDIR}$(libdir)"
+ #$(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${libdir}/${PLUGIN_NAME}.so"
+
+uninstall :
+ -rm -f "${DESTDIR}${libdir}/${PLUGIN_NAME}.so"
diff --git a/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c b/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c
new file mode 100644
index 00000000..191cf710
--- /dev/null
+++ b/plugins/deny-protocol-version/mosquitto_deny_protocol_version.c
@@ -0,0 +1,110 @@
+/*
+Copyright (c) 2022 Roger Light
+
+All rights reserved. This program and the accompanying materials
+are made available under the terms of the Eclipse Public License 2.0
+and Eclipse Distribution License v1.0 which accompany this distribution.
+
+The Eclipse Public License is available at
+ https://www.eclipse.org/legal/epl-2.0/
+and the Eclipse Distribution License is available at
+ http://www.eclipse.org/org/documents/edl-v10.php.
+
+SPDX-License-Identifier: EPL-2.0 OR EDL-1.0
+
+Contributors:
+ Roger Light - initial implementation and documentation.
+*/
+
+/*
+ * This is an example plugin showing how to deny access based on the version of
+ * the protocol spec a client connects with. It does no other authentication
+ * checks.
+ *
+ * It could be used with other authentication plugins by specifying it in the
+ * config file before another plugin, for example:
+ *
+ * plugin /usr/lib/mosquitto_deny_protocol_version.so
+ * plugin /usr/lib/mosquitto_dynamic_security.so
+ *
+ * or:
+ *
+ * plugin /usr/lib/mosquitto_deny_protocol_version.so
+ * password_file pwfile
+ *
+ * It will *not* work on its own.
+ *
+ * In Mosquitto 2.1, this can be achieved with the `accept_protocol_version`
+ * option instead.
+ *
+ *
+ * To compile:
+ *
+ * gcc -I -fPIC -shared mosquitto_deny_protocol_version.c -o mosquitto_deny_protocol_version.so
+ *
+ * Note that this only works on Mosquitto 2.0 or later.
+ */
+#include "config.h"
+
+#include
+#include
+
+#include "mosquitto_broker.h"
+#include "mosquitto_plugin.h"
+#include "mosquitto.h"
+#include "mqtt_protocol.h"
+
+static mosquitto_plugin_id_t *mosq_pid = NULL;
+
+int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
+{
+ int i;
+
+ for(i=0; iclient);
+
+ if(protocol_version == 5 || protocol_version == 4){
+ /* Allow access to MQTT v5.0 and v3.1.1 - this passes on responsibility
+ * for the actual auth checks to the next plugin/password file in the
+ * config list. If no other plugins/password file is defined, then
+ * access will be denied. */
+ return MOSQ_ERR_PLUGIN_DEFER;
+ }else{
+ /* Deny access to all others */
+ return MOSQ_ERR_AUTH;
+ }
+}
+
+int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count)
+{
+ UNUSED(user_data);
+ UNUSED(opts);
+ UNUSED(opt_count);
+
+ mosq_pid = identifier;
+ return mosquitto_callback_register(mosq_pid, MOSQ_EVT_BASIC_AUTH, basic_auth_callback, NULL, NULL);
+}
+
+int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int opt_count)
+{
+ UNUSED(user_data);
+ UNUSED(opts);
+ UNUSED(opt_count);
+
+ return mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_MESSAGE, basic_auth_callback, NULL);
+}
diff --git a/plugins/deny-protocol-version/test.conf b/plugins/deny-protocol-version/test.conf
new file mode 100644
index 00000000..01021314
--- /dev/null
+++ b/plugins/deny-protocol-version/test.conf
@@ -0,0 +1,4 @@
+listener 1883
+
+plugin ./mosquitto_deny_protocol_version.so
+password_file pwfile
diff --git a/plugins/deny-protocol-version/test.sh b/plugins/deny-protocol-version/test.sh
new file mode 100755
index 00000000..3005da50
--- /dev/null
+++ b/plugins/deny-protocol-version/test.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+../../apps/mosquitto_passwd/mosquitto_passwd -c -b pwfile username password
+../../src/mosquitto -c test.conf -v
diff --git a/plugins/dynamic-security/clients.c b/plugins/dynamic-security/clients.c
index a2c7cbd9..c75a9379 100644
--- a/plugins/dynamic-security/clients.c
+++ b/plugins/dynamic-security/clients.c
@@ -718,10 +718,12 @@ static void client__remove_all_roles(struct dynsec__client *client)
int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd *cmd, struct mosquitto *context)
{
char *username;
- char *clientid;
- char *password;
- char *text_name, *text_description;
+ char *clientid = NULL;
+ char *password = NULL;
+ char *text_name = NULL, *text_description = NULL;
+ bool have_clientid = false, have_text_name = false, have_text_description = false, have_rolelist = false, have_password = false;
struct dynsec__client *client;
+ struct dynsec__group *group;
struct dynsec__rolelist *rolelist = NULL;
char *str;
int rc;
@@ -744,81 +746,87 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd
return MOSQ_ERR_INVAL;
}
- if(json_get_string(cmd->j_command, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){
- if(clientid && strlen(clientid) > 0){
- str = mosquitto_strdup(clientid);
- if(str == NULL){
+ if(json_get_string(cmd->j_command, "clientid", &str, false) == MOSQ_ERR_SUCCESS){
+ have_clientid = true;
+ if(str && strlen(str) > 0){
+ clientid = mosquitto_strdup(str);
+ if(clientid == NULL){
plugin__command_reply(cmd, "Internal error");
- return MOSQ_ERR_NOMEM;
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
}
}else{
- str = NULL;
+ clientid = NULL;
}
- mosquitto_free(client->clientid);
- client->clientid = str;
}
if(json_get_string(cmd->j_command, "password", &password, false) == MOSQ_ERR_SUCCESS){
if(strlen(password) > 0){
- /* If password == "", we just ignore it */
- rc = client__set_password(client, password);
- if(rc != MOSQ_ERR_SUCCESS){
- plugin__command_reply(cmd, "Internal error");
- dynsec_kicklist__add(data, username);
- return MOSQ_ERR_NOMEM;
- }
+ have_password = true;
}
}
- if(json_get_string(cmd->j_command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){
- str = mosquitto_strdup(text_name);
- if(str == NULL){
+ if(json_get_string(cmd->j_command, "textname", &str, false) == MOSQ_ERR_SUCCESS){
+ have_text_name = true;
+ text_name = mosquitto_strdup(str);
+ if(text_name == NULL){
plugin__command_reply(cmd, "Internal error");
- dynsec_kicklist__add(data, username);
- return MOSQ_ERR_NOMEM;
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
}
- mosquitto_free(client->text_name);
- client->text_name = str;
}
- if(json_get_string(cmd->j_command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){
- str = mosquitto_strdup(text_description);
- if(str == NULL){
+ if(json_get_string(cmd->j_command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){
+ have_text_description = true;
+ text_description = mosquitto_strdup(str);
+ if(text_description == NULL){
plugin__command_reply(cmd, "Internal error");
- dynsec_kicklist__add(data, username);
- return MOSQ_ERR_NOMEM;
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
}
- mosquitto_free(client->text_description);
- client->text_description = str;
}
rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &rolelist);
if(rc == MOSQ_ERR_SUCCESS){
- client__remove_all_roles(client);
- client__add_new_roles(client, rolelist);
- dynsec_rolelist__cleanup(&rolelist);
+ have_rolelist = true;
}else if(rc == ERR_LIST_NOT_FOUND){
/* There was no list in the JSON, so no modification */
}else if(rc == MOSQ_ERR_NOT_FOUND){
plugin__command_reply(cmd, "Role not found");
- dynsec_rolelist__cleanup(&rolelist);
- dynsec_kicklist__add(data, username);
- return MOSQ_ERR_INVAL;
+ rc = MOSQ_ERR_INVAL;
+ goto error;
}else{
if(rc == MOSQ_ERR_INVAL){
plugin__command_reply(cmd, "'roles' not an array or missing/invalid rolename");
}else{
plugin__command_reply(cmd, "Internal error");
}
- dynsec_rolelist__cleanup(&rolelist);
- dynsec_kicklist__add(data, username);
- return MOSQ_ERR_INVAL;
+ rc = MOSQ_ERR_INVAL;
+ goto error;
}
j_groups = cJSON_GetObjectItem(cmd->j_command, "groups");
if(j_groups && cJSON_IsArray(j_groups)){
- dynsec__remove_client_from_all_groups(data, username);
+ /* 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(data, jtmp->valuestring);
+ if(group == NULL){
+ plugin__command_reply(cmd, "'groups' contains an object with a 'groupname' that does not exist");
+ rc = MOSQ_ERR_INVAL;
+ goto error;
+ }
+ }else{
+ plugin__command_reply(cmd, "'groups' contains an object with an invalid 'groupname'");
+ rc = MOSQ_ERR_INVAL;
+ goto error;
+ }
+ }
+ }
+ dynsec__remove_client_from_all_groups(data, username);
cJSON_ArrayForEach(j_group, j_groups){
if(cJSON_IsObject(j_group)){
jtmp = cJSON_GetObjectItem(j_group, "groupname");
@@ -830,6 +838,44 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd
}
}
+ if(have_password){
+ /* FIXME - This is the one call that will result in modification on internal error - note that groups have already been modified */
+ rc = client__set_password(client, password);
+ if(rc != MOSQ_ERR_SUCCESS){
+ plugin__command_reply(cmd, "Internal error");
+ mosquitto_kick_client_by_username(username, false);
+ /* If this fails we have the situation that the password is set as
+ * invalid, but the config isn't saved, so restarting the broker
+ * *now* will mean the client can log in again. This might be
+ * "good", but is inconsistent, so save the config to be
+ * consistent. */
+ dynsec__config_save(data);
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
+ }
+ }
+
+ if(have_clientid){
+ mosquitto_free(client->clientid);
+ client->clientid = clientid;
+ }
+
+ if(have_text_name){
+ mosquitto_free(client->text_name);
+ client->text_name = text_name;
+ }
+
+ if(have_text_description){
+ mosquitto_free(client->text_description);
+ client->text_description = text_description;
+ }
+
+ if(have_rolelist){
+ client__remove_all_roles(client);
+ client__add_new_roles(client, rolelist);
+ dynsec_rolelist__cleanup(&rolelist);
+ }
+
dynsec__config_save(data);
plugin__command_reply(cmd, NULL);
@@ -841,6 +887,12 @@ int dynsec_clients__process_modify(struct dynsec__data *data, struct plugin_cmd
mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyClient | username=%s",
admin_clientid, admin_username, username);
return MOSQ_ERR_SUCCESS;
+error:
+ mosquitto_free(clientid);
+ mosquitto_free(text_name);
+ mosquitto_free(text_description);
+ dynsec_rolelist__cleanup(&rolelist);
+ return rc;
}
diff --git a/plugins/dynamic-security/dynamic_security.h b/plugins/dynamic-security/dynamic_security.h
index a236ae23..36483bc1 100644
--- a/plugins/dynamic-security/dynamic_security.h
+++ b/plugins/dynamic-security/dynamic_security.h
@@ -305,5 +305,6 @@ cJSON *dynsec_rolelist__all_to_json(struct dynsec__rolelist *base_rolelist);
int dynsec_kicklist__add(struct dynsec__data *data, const char *username);
void dynsec_kicklist__kick(struct dynsec__data *data);
int dynsec__tick_callback(int event, void *event_data, void *userdata);
+void dynsec_kicklist__cleanup(struct dynsec__data *data);
#endif
diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c
index 2f718289..7b81495c 100644
--- a/plugins/dynamic-security/groups.c
+++ b/plugins/dynamic-security/groups.c
@@ -909,10 +909,12 @@ int dynsec_groups__process_remove_role(struct dynsec__data *data, struct plugin_
int dynsec_groups__process_modify(struct dynsec__data *data, struct plugin_cmd *cmd, struct mosquitto *context)
{
- char *groupname;
- char *text_name, *text_description;
- struct dynsec__group *group;
+ char *groupname = NULL;
+ char *text_name = NULL, *text_description = NULL;
+ struct dynsec__client *client = NULL;
+ 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;
@@ -934,52 +936,73 @@ int dynsec_groups__process_modify(struct dynsec__data *data, struct plugin_cmd *
return MOSQ_ERR_INVAL;
}
- if(json_get_string(cmd->j_command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){
- str = mosquitto_strdup(text_name);
- if(str == NULL){
+ if(json_get_string(cmd->j_command, "textname", &str, false) == MOSQ_ERR_SUCCESS){
+ have_text_name = true;
+ text_name = mosquitto_strdup(str);
+ if(text_name == NULL){
plugin__command_reply(cmd, "Internal error");
- return MOSQ_ERR_NOMEM;
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
}
- mosquitto_free(group->text_name);
- group->text_name = str;
}
- if(json_get_string(cmd->j_command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){
- str = mosquitto_strdup(text_description);
- if(str == NULL){
+ if(json_get_string(cmd->j_command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){
+ have_text_description = true;
+ text_description = mosquitto_strdup(str);
+ if(text_description == NULL){
plugin__command_reply(cmd, "Internal error");
- return MOSQ_ERR_NOMEM;
+ rc = MOSQ_ERR_NOMEM;
+ goto error;
}
- mosquitto_free(group->text_description);
- group->text_description = str;
}
rc = dynsec_rolelist__load_from_json(data, cmd->j_command, &rolelist);
if(rc == MOSQ_ERR_SUCCESS){
- dynsec_rolelist__cleanup(&group->rolelist);
- group->rolelist = rolelist;
+ /* Apply changes below */
+ have_rolelist = true;
}else if(rc == ERR_LIST_NOT_FOUND){
/* There was no list in the JSON, so no modification */
+ rolelist = NULL;
}else if(rc == MOSQ_ERR_NOT_FOUND){
plugin__command_reply(cmd, "Role not found");
- dynsec_rolelist__cleanup(&rolelist);
- group__kick_all(data, group);
- return MOSQ_ERR_INVAL;
+ rc = MOSQ_ERR_INVAL;
+ goto error;
}else{
if(rc == MOSQ_ERR_INVAL){
plugin__command_reply(cmd, "'roles' not an array or missing/invalid rolename");
}else{
plugin__command_reply(cmd, "Internal error");
}
- dynsec_rolelist__cleanup(&rolelist);
- group__kick_all(data, group);
- return MOSQ_ERR_INVAL;
+ rc = MOSQ_ERR_INVAL;
+ goto error;
}
j_clients = cJSON_GetObjectItem(cmd->j_command, "clients");
if(j_clients && cJSON_IsArray(j_clients)){
+ /* 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(data, jtmp->valuestring);
+ if(client == NULL){
+ plugin__command_reply(cmd, "'clients' contains an object with a 'username' that does not exist");
+ rc = MOSQ_ERR_INVAL;
+ goto error;
+ }
+ }else{
+ plugin__command_reply(cmd, "'clients' contains an object with an invalid 'username'");
+ rc = MOSQ_ERR_INVAL;
+ goto error;
+ }
+ }
+ }
+
+ /* Kick all clients in the *current* group */
+ group__kick_all(data, group);
dynsec__remove_all_clients_from_group(group);
+ /* 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");
@@ -991,11 +1014,28 @@ int dynsec_groups__process_modify(struct dynsec__data *data, struct plugin_cmd *
}
}
+ /* Apply remaining changes to group, note that user changes are already applied */
+ if(have_text_name){
+ mosquitto_free(group->text_name);
+ group->text_name = text_name;
+ }
+
+ if(have_text_description){
+ mosquitto_free(group->text_description);
+ group->text_description = text_description;
+ }
+
+ if(have_rolelist){
+ dynsec_rolelist__cleanup(&group->rolelist);
+ group->rolelist = rolelist;
+ }
+
+ /* And save */
dynsec__config_save(data);
plugin__command_reply(cmd, NULL);
- /* Enforce any changes */
+ /* Enforce any changes - kick any clients in the *new* group */
group__kick_all(data, group);
admin_clientid = mosquitto_client_id(context);
@@ -1004,6 +1044,17 @@ int dynsec_groups__process_modify(struct dynsec__data *data, struct plugin_cmd *
admin_clientid, admin_username, groupname);
return MOSQ_ERR_SUCCESS;
+error:
+ mosquitto_free(text_name);
+ mosquitto_free(text_description);
+ dynsec_rolelist__cleanup(&rolelist);
+
+ admin_clientid = mosquitto_client_id(context);
+ admin_username = mosquitto_client_username(context);
+ mosquitto_log_printf(MOSQ_LOG_INFO, "dynsec: %s/%s | modifyGroup | groupname=%s",
+ admin_clientid, admin_username, groupname);
+
+ return rc;
}
diff --git a/plugins/dynamic-security/kicklist.c b/plugins/dynamic-security/kicklist.c
index fd06e081..2d8ab329 100644
--- a/plugins/dynamic-security/kicklist.c
+++ b/plugins/dynamic-security/kicklist.c
@@ -64,3 +64,13 @@ void dynsec_kicklist__kick(struct dynsec__data *data)
free(kick);
}
}
+
+void dynsec_kicklist__cleanup(struct dynsec__data *data)
+{
+ struct dynsec__kicklist *kick, *tmp;
+
+ DL_FOREACH_SAFE(data->kicklist, kick, tmp){
+ DL_DELETE(data->kicklist, kick);
+ free(kick);
+ }
+}
diff --git a/plugins/dynamic-security/plugin.c b/plugins/dynamic-security/plugin.c
index 788efacd..15e2a8d2 100644
--- a/plugins/dynamic-security/plugin.c
+++ b/plugins/dynamic-security/plugin.c
@@ -24,6 +24,11 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
+#include "json_help.h"
#include "mosquitto.h"
#include "mosquitto_broker.h"
#include "mosquitto_plugin.h"
@@ -39,6 +44,7 @@ static mosquitto_plugin_id_t *plg_id = NULL;
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *options, int option_count)
{
int i;
+ int rc;
UNUSED(user_data);
@@ -66,12 +72,54 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, s
mosquitto_plugin_set_info(identifier, "dynamic-security", NULL);
dynsec__config_load(&dynsec_data);
- mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", &dynsec_data);
- mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, &dynsec_data);
- mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, &dynsec_data);
- mosquitto_callback_register(plg_id, MOSQ_EVT_TICK, dynsec__tick_callback, NULL, &dynsec_data);
+
+ rc = mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", &dynsec_data);
+ if(rc == MOSQ_ERR_ALREADY_EXISTS){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Dynamic security plugin can currently only be loaded once.");
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Note that this was previously incorrectly allowed but could cause problems with duplicate entries in the config.");
+ goto error;
+ }else if(rc == MOSQ_ERR_NOMEM){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
+ goto error;
+ }else if(rc != MOSQ_ERR_SUCCESS){
+ goto error;
+ }
+
+ rc = mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, &dynsec_data);
+ if(rc == MOSQ_ERR_ALREADY_EXISTS){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Dynamic security plugin can only be loaded once.");
+ goto error;
+ }else if(rc == MOSQ_ERR_NOMEM){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
+ goto error;
+ }else if(rc != MOSQ_ERR_SUCCESS){
+ goto error;
+ }
+
+ rc = mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, &dynsec_data);
+ if(rc == MOSQ_ERR_ALREADY_EXISTS){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Dynamic security plugin can only be loaded once.");
+ goto error;
+ }else if(rc == MOSQ_ERR_NOMEM){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
+ goto error;
+ }else if(rc != MOSQ_ERR_SUCCESS){
+ goto error;
+ }
+
+ rc = mosquitto_callback_register(plg_id, MOSQ_EVT_TICK, dynsec__tick_callback, NULL, &dynsec_data);
+ if(rc == MOSQ_ERR_NOMEM){
+ mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
+ goto error;
+ }else if(rc != MOSQ_ERR_SUCCESS){
+ goto error;
+ }
return MOSQ_ERR_SUCCESS;
+error:
+ mosquitto_free(dynsec_data.config_file);
+ dynsec_data.config_file = NULL;
+ return rc;
}
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *options, int option_count)
@@ -83,6 +131,7 @@ int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *options, int
dynsec_groups__cleanup(&dynsec_data);
dynsec_clients__cleanup(&dynsec_data);
dynsec_roles__cleanup(&dynsec_data);
+ dynsec_kicklist__cleanup(&dynsec_data);
mosquitto_free(dynsec_data.config_file);
dynsec_data.config_file = NULL;
diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c
index 321bbd60..d69be20d 100644
--- a/plugins/dynamic-security/roles.c
+++ b/plugins/dynamic-security/roles.c
@@ -24,6 +24,10 @@ Contributors:
#include
#include
+#ifndef WIN32
+# include
+#endif
+
#include "dynamic_security.h"
#include "json_help.h"
#include "mosquitto.h"
diff --git a/service/systemd/mosquitto.service.notify b/service/systemd/mosquitto.service.notify
index d88a8868..06772dda 100644
--- a/service/systemd/mosquitto.service.notify
+++ b/service/systemd/mosquitto.service.notify
@@ -11,9 +11,9 @@ ExecStart=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
ExecStartPre=/bin/mkdir -m 740 -p /var/log/mosquitto
-ExecStartPre=/bin/chown mosquitto /var/log/mosquitto
+ExecStartPre=/bin/chown mosquitto:mosquitto /var/log/mosquitto
ExecStartPre=/bin/mkdir -m 740 -p /run/mosquitto
-ExecStartPre=/bin/chown mosquitto /run/mosquitto
+ExecStartPre=/bin/chown mosquitto:mosquitto /run/mosquitto
[Install]
WantedBy=multi-user.target
diff --git a/service/systemd/mosquitto.service.simple b/service/systemd/mosquitto.service.simple
index 80efecd4..15ee0d62 100644
--- a/service/systemd/mosquitto.service.simple
+++ b/service/systemd/mosquitto.service.simple
@@ -9,9 +9,9 @@ ExecStart=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
ExecStartPre=/bin/mkdir -m 740 -p /var/log/mosquitto
-ExecStartPre=/bin/chown mosquitto /var/log/mosquitto
+ExecStartPre=/bin/chown mosquitto:mosquitto /var/log/mosquitto
ExecStartPre=/bin/mkdir -m 740 -p /run/mosquitto
-ExecStartPre=/bin/chown mosquitto /run/mosquitto
+ExecStartPre=/bin/chown mosquitto:mosquitto /run/mosquitto
[Install]
WantedBy=multi-user.target
diff --git a/src/conf.c b/src/conf.c
index 9e61e115..3f1d9b35 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -2174,6 +2174,8 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload,
return MOSQ_ERR_INVAL;
}
cur_bridge->restart_timeout = atoi(token);
+ cur_bridge->backoff_base = 0;
+ cur_bridge->backoff_cap = 0;
if(cur_bridge->restart_timeout < 1){
log__printf(NULL, MOSQ_LOG_NOTICE, "restart_timeout interval too low, using 1 second.");
cur_bridge->restart_timeout = 1;
diff --git a/src/database.c b/src/database.c
index f73e84b8..24e2fabd 100644
--- a/src/database.c
+++ b/src/database.c
@@ -1223,6 +1223,40 @@ int db__message_release_incoming(struct mosquitto *context, uint16_t mid)
}
}
+
+void db__expire_all_messages(struct mosquitto *context)
+{
+ struct mosquitto_client_msg *msg, *tmp;
+
+ DL_FOREACH_SAFE(context->msgs_out.inflight, msg, tmp){
+ if(msg->base_msg->message_expiry_time && db.now_real_s > msg->base_msg->message_expiry_time){
+ if(msg->qos > 0){
+ util__increment_send_quota(context);
+ }
+ db__message_remove_inflight(context, &context->msgs_out, msg);
+ }
+ }
+ DL_FOREACH_SAFE(context->msgs_out.queued, msg, tmp){
+ if(msg->base_msg->message_expiry_time && db.now_real_s > msg->base_msg->message_expiry_time){
+ db__message_remove_queued(context, &context->msgs_out, msg);
+ }
+ }
+ DL_FOREACH_SAFE(context->msgs_in.inflight, msg, tmp){
+ if(msg->base_msg->message_expiry_time && db.now_real_s > msg->base_msg->message_expiry_time){
+ if(msg->qos > 0){
+ util__increment_receive_quota(context);
+ }
+ db__message_remove_inflight(context, &context->msgs_in, msg);
+ }
+ }
+ DL_FOREACH_SAFE(context->msgs_in.queued, msg, tmp){
+ if(msg->base_msg->message_expiry_time && db.now_real_s > msg->base_msg->message_expiry_time){
+ db__message_remove_queued(context, &context->msgs_in, msg);
+ }
+ }
+}
+
+
static int db__message_write_inflight_out_single(struct mosquitto *context, struct mosquitto_client_msg *msg)
{
mosquitto_property *base_msg_props = NULL;
@@ -1392,7 +1426,7 @@ int db__message_write_queued_in(struct mosquitto *context)
}
DL_FOREACH_SAFE(context->msgs_in.queued, tail, tmp){
- if(context->msgs_out.inflight_maximum != 0 && context->msgs_in.inflight_quota == 0){
+ if(context->msgs_in.inflight_maximum != 0 && context->msgs_in.inflight_quota == 0){
break;
}
diff --git a/src/handle_connack.c b/src/handle_connack.c
index 4906ffb5..925084ac 100644
--- a/src/handle_connack.c
+++ b/src/handle_connack.c
@@ -175,7 +175,7 @@ int handle__connack(struct mosquitto *context)
log__printf(NULL, MOSQ_LOG_ERR, "Connection Refused: broker unavailable");
return MOSQ_ERR_CONN_LOST;
case CONNACK_REFUSED_BAD_USERNAME_PASSWORD:
- log__printf(NULL, MOSQ_LOG_ERR, "Connection Refused: broker unavailable");
+ log__printf(NULL, MOSQ_LOG_ERR, "Connection Refused: bad user name or password");
return MOSQ_ERR_CONN_LOST;
case CONNACK_REFUSED_NOT_AUTHORIZED:
log__printf(NULL, MOSQ_LOG_ERR, "Connection Refused: not authorised");
diff --git a/src/handle_connect.c b/src/handle_connect.c
index 18ed4679..cc89d58a 100644
--- a/src/handle_connect.c
+++ b/src/handle_connect.c
@@ -334,6 +334,7 @@ int connect__on_authorised(struct mosquitto *context, void *auth_data_out, uint1
rc = send__connack(context, connect_ack, CONNACK_ACCEPTED, connack_props);
mosquitto_property_free_all(&connack_props);
if(rc) return rc;
+ db__expire_all_messages(context);
rc = db__message_write_queued_out(context);
if(rc) return rc;
rc = db__message_write_inflight_out_all(context);
@@ -600,9 +601,6 @@ int handle__connect(struct mosquitto *context)
rc = MOSQ_ERR_PROTOCOL;
goto handle_connect_error;
}
- if(context->in_packet.command != CMD_CONNECT){
- return MOSQ_ERR_MALFORMED_PACKET;
- }
/* Read protocol name as length then bytes rather than with read_string
* because the length is fixed and we can check that. Removes the need
@@ -681,6 +679,9 @@ int handle__connect(struct mosquitto *context)
rc = MOSQ_ERR_PROTOCOL;
goto handle_connect_error;
}
+ if((protocol_version&0x7F) != PROTOCOL_VERSION_v31 && context->in_packet.command != CMD_CONNECT){
+ return MOSQ_ERR_MALFORMED_PACKET;
+ }
if(packet__read_byte(&context->in_packet, &connect_flags)){
rc = MOSQ_ERR_PROTOCOL;
diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h
index 467fc0aa..796a780c 100644
--- a/src/mosquitto_broker_internal.h
+++ b/src/mosquitto_broker_internal.h
@@ -753,6 +753,7 @@ int db__message_write_queued_in(struct mosquitto *context);
void db__msg_add_to_inflight_stats(struct mosquitto_msg_data *msg_data, struct mosquitto_client_msg *msg);
void db__msg_add_to_queued_stats(struct mosquitto_msg_data *msg_data, struct mosquitto_client_msg *msg);
uint64_t db__new_msg_id(void);
+void db__expire_all_messages(struct mosquitto *context);
/* ============================================================
* Subscription functions
@@ -933,6 +934,7 @@ void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *
* Session expiry
* ============================================================ */
int session_expiry__add(struct mosquitto *context);
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time);
void session_expiry__remove(struct mosquitto *context);
void session_expiry__remove_all(void);
void session_expiry__check(void);
diff --git a/src/net.c b/src/net.c
index cb75ce9b..f79b7ebc 100644
--- a/src/net.c
+++ b/src/net.c
@@ -19,15 +19,16 @@ Contributors:
#include "config.h"
#ifndef WIN32
-#include
-#include
-#include
-#include
-#include
-#include
+# include
+# include
+# include
+# include
+# include
+# include
+# include
#else
-#include
-#include
+# include
+# include
#endif
#include
@@ -36,7 +37,7 @@ Contributors:
#include
#include
#ifdef WITH_WRAP
-#include
+# include
#endif
#ifdef HAVE_NETINET_IN_H
@@ -49,7 +50,7 @@ Contributors:
#endif
#ifdef __QNX__
-#include
+# include
#endif
#include "mosquitto_broker_internal.h"
@@ -59,8 +60,8 @@ Contributors:
#include "util_mosq.h"
#ifdef WITH_TLS
-#include "tls_mosq.h"
-#include
+# include "tls_mosq.h"
+# include
static int tls_ex_index_context = -1;
static int tls_ex_index_listener = -1;
#endif
@@ -611,7 +612,7 @@ int net__tls_load_verify(struct mosquitto__listener *listener)
#ifdef WITH_TLS
int rc;
-#if OPENSSL_VERSION_NUMBER < 0x30000000L
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
if(listener->cafile || listener->capath){
rc = SSL_CTX_load_verify_locations(listener->ssl_ctx, listener->cafile, listener->capath);
if(rc == 0){
@@ -624,7 +625,7 @@ int net__tls_load_verify(struct mosquitto__listener *listener)
}
}
}
-#else
+# else
if(listener->cafile){
rc = SSL_CTX_load_verify_file(listener->ssl_ctx, listener->cafile);
if(rc == 0){
@@ -641,11 +642,13 @@ int net__tls_load_verify(struct mosquitto__listener *listener)
return MOSQ_ERR_TLS;
}
}
-#endif
+# endif
+# if !defined(OPENSSL_NO_ENGINE)
if(net__load_engine(listener)){
return MOSQ_ERR_TLS;
}
+# endif
#endif
return net__load_certificates(listener);
}
diff --git a/src/persist_read.c b/src/persist_read.c
index ac56b3b8..7e189361 100644
--- a/src/persist_read.c
+++ b/src/persist_read.c
@@ -208,7 +208,7 @@ static int persist__client_chunk_restore(FILE *db_fptr)
}
}
}
- /* FIXME - we should expire clients here if they have exceeded their time */
+ session_expiry__add_from_persistence(context, chunk.F.session_expiry_time);
}else{
rc = 1;
}
diff --git a/src/persist_write.c b/src/persist_write.c
index eb429782..eec74ee0 100644
--- a/src/persist_write.c
+++ b/src/persist_write.c
@@ -164,8 +164,17 @@ static int persist__client_save(FILE *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){
+ if(context && (context->clean_start == false
+#ifdef WITH_BRIDGE
+ || (context->bridge && context->bridge->clean_start_local == 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;
+ }else{
+ chunk.F.session_expiry_time = context->session_expiry_time;
+ }
chunk.F.session_expiry_interval = context->session_expiry_interval;
chunk.F.last_mid = context->last_mid;
chunk.F.id_len = (uint16_t)strlen(context->id);
diff --git a/src/session_expiry.c b/src/session_expiry.c
index 4e3ea001..9f1cd86f 100644
--- a/src/session_expiry.c
+++ b/src/session_expiry.c
@@ -84,6 +84,31 @@ int session_expiry__add(struct mosquitto *context)
}
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
+{
+ struct session_expiry_list *item;
+
+ if(db.config->persistent_client_expiration == 0){
+ if(context->session_expiry_interval == UINT32_MAX){
+ /* There isn't a global expiry set, and the client has asked to
+ * never expire, so we don't add it to the list. */
+ return MOSQ_ERR_SUCCESS;
+ }
+ }
+
+ item = mosquitto__calloc(1, sizeof(struct session_expiry_list));
+ if(!item) return MOSQ_ERR_NOMEM;
+
+ item->context = context;
+ item->context->session_expiry_time = expiry_time;
+ context->expiry_list_item = item;
+
+ DL_INSERT_INORDER(expiry_list, item, session_expiry__cmp);
+
+ return MOSQ_ERR_SUCCESS;
+}
+
+
void session_expiry__remove(struct mosquitto *context)
{
if(context->expiry_list_item){
diff --git a/src/websockets.c b/src/websockets.c
index 03cd49ea..20e3f345 100644
--- a/src/websockets.c
+++ b/src/websockets.c
@@ -136,6 +136,7 @@ static int callback_mqtt(
uint8_t *buf;
int rc;
uint8_t byte;
+ char ip_addr_buff[1024];
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
@@ -160,7 +161,12 @@ static int callback_mqtt(
}else{
return -1;
}
- easy_address(lws_get_socket_fd(wsi), mosq);
+
+ if (lws_hdr_copy(wsi, ip_addr_buff, sizeof(ip_addr_buff), WSI_TOKEN_X_FORWARDED_FOR) > 0) {
+ mosq->address = mosquitto__strdup(ip_addr_buff);
+ } else {
+ easy_address(lws_get_socket_fd(wsi), mosq);
+ }
if(!mosq->address){
/* getpeername and inet_ntop failed and not a bridge */
mosquitto__FREE(mosq);
@@ -246,7 +252,7 @@ static int callback_mqtt(
#ifdef WITH_SYS_TREE
g_msgs_sent++;
- if(((packet->command)&0xF6) == CMD_PUBLISH){
+ if(((packet->command)&0xF0) == CMD_PUBLISH){
g_pub_msgs_sent++;
}
#endif
@@ -331,7 +337,7 @@ static int callback_mqtt(
#ifdef WITH_SYS_TREE
G_MSGS_RECEIVED_INC(1);
- if(((mosq->in_packet.command)&0xF5) == CMD_PUBLISH){
+ if(((mosq->in_packet.command)&0xF0) == CMD_PUBLISH){
G_PUB_MSGS_RECEIVED_INC(1);
}
#endif
diff --git a/test/broker/08-ssl-bridge.py b/test/broker/08-ssl-bridge.py
index 9923a2a8..de765254 100755
--- a/test/broker/08-ssl-bridge.py
+++ b/test/broker/08-ssl-bridge.py
@@ -36,13 +36,9 @@ publish_packet = mosq_test.gen_publish("bridge/ssl/test", qos=0, payload="messag
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-ssock = ssl.wrap_socket(
- sock,
- ca_certs=ssl_dir / "all-ca.crt",
- keyfile=ssl_dir / "server.key",
- certfile=ssl_dir / "server.crt",
- server_side=True
-)
+context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=f"{ssl_dir}/all-ca.crt")
+context.load_cert_chain(certfile=f"{ssl_dir}/server.crt", keyfile=f"{ssl_dir}/server.key")
+ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(20)
ssock.bind(('', port1))
ssock.listen(5)
diff --git a/test/broker/08-ssl-connect-cert-auth-crl.py b/test/broker/08-ssl-connect-cert-auth-crl.py
index 85a89f08..6834d490 100755
--- a/test/broker/08-ssl-connect-cert-auth-crl.py
+++ b/test/broker/08-ssl-connect-cert-auth-crl.py
@@ -30,7 +30,9 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-cert-auth-expired.py b/test/broker/08-ssl-connect-cert-auth-expired.py
index 14e6225e..8a9d3d65 100755
--- a/test/broker/08-ssl-connect-cert-auth-expired.py
+++ b/test/broker/08-ssl-connect-cert-auth-expired.py
@@ -30,7 +30,9 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client-expired.crt", keyfile=f"{ssl_dir}/client-expired.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client-expired.crt", keyfile=f"{ssl_dir}/client-expired.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-cert-auth-revoked.py b/test/broker/08-ssl-connect-cert-auth-revoked.py
index f7057c5b..ed3ee0de 100755
--- a/test/broker/08-ssl-connect-cert-auth-revoked.py
+++ b/test/broker/08-ssl-connect-cert-auth-revoked.py
@@ -29,7 +29,9 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client-revoked.crt", keyfile=f"{ssl_dir}/client-revoked.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client-revoked.crt", keyfile=f"{ssl_dir}/client-revoked.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-cert-auth-without.py b/test/broker/08-ssl-connect-cert-auth-without.py
index 7377e1bf..666af7d6 100755
--- a/test/broker/08-ssl-connect-cert-auth-without.py
+++ b/test/broker/08-ssl-connect-cert-auth-without.py
@@ -27,7 +27,8 @@ connect_packet = mosq_test.gen_connect("connect-cert-test")
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2, use_conf=True)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
+context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-cert-auth.py b/test/broker/08-ssl-connect-cert-auth.py
index bd8c93fb..c60d950e 100755
--- a/test/broker/08-ssl-connect-cert-auth.py
+++ b/test/broker/08-ssl-connect-cert-auth.py
@@ -31,7 +31,9 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-identity.py b/test/broker/08-ssl-connect-identity.py
index bd1e76d2..0b1ec180 100755
--- a/test/broker/08-ssl-connect-identity.py
+++ b/test/broker/08-ssl-connect-identity.py
@@ -32,7 +32,9 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-no-auth-wrong-ca.py b/test/broker/08-ssl-connect-no-auth-wrong-ca.py
index 52a71b80..b57f467f 100755
--- a/test/broker/08-ssl-connect-no-auth-wrong-ca.py
+++ b/test/broker/08-ssl-connect-no-auth-wrong-ca.py
@@ -28,7 +28,8 @@ connack_packet = mosq_test.gen_connack(rc=0)
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2, use_conf=True)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-alt-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
+context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-alt-ca.crt")
+ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-no-auth.py b/test/broker/08-ssl-connect-no-auth.py
index c9134132..8d94bbcb 100755
--- a/test/broker/08-ssl-connect-no-auth.py
+++ b/test/broker/08-ssl-connect-no-auth.py
@@ -31,7 +31,8 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-connect-no-identity.py b/test/broker/08-ssl-connect-no-identity.py
index 1573c609..631dd3f1 100755
--- a/test/broker/08-ssl-connect-no-identity.py
+++ b/test/broker/08-ssl-connect-no-identity.py
@@ -31,7 +31,8 @@ broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port2,
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))
diff --git a/test/broker/08-ssl-hup-disconnect.py b/test/broker/08-ssl-hup-disconnect.py
index 8de8c500..1fbfac20 100755
--- a/test/broker/08-ssl-hup-disconnect.py
+++ b/test/broker/08-ssl-hup-disconnect.py
@@ -42,7 +42,9 @@ def do_test(option):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/test-root-ca.crt", certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key", cert_reqs=ssl.CERT_REQUIRED)
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-root-ca.crt")
+ context.load_cert_chain(certfile=f"{ssl_dir}/client.crt", keyfile=f"{ssl_dir}/client.key")
+ ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port))
mosq_test.do_send_receive(ssock, connect_packet, connack_packet, "connack")
diff --git a/test/broker/09-plugin-tick.py b/test/broker/09-plugin-tick.py
new file mode 100755
index 00000000..f6af1dbd
--- /dev/null
+++ b/test/broker/09-plugin-tick.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+# Test whether a plugin can subscribe to the tick event
+
+from mosq_test_helper import *
+
+def write_config(filename, port, per_listener_settings="false"):
+ with open(filename, 'w') as f:
+ f.write("per_listener_settings %s\n" % (per_listener_settings))
+ f.write("listener %d\n" % (port))
+ f.write("plugin c/auth_plugin_v5_handle_tick.so\n")
+ f.write("allow_anonymous true\n")
+
+def do_test(per_listener_settings):
+ proto_ver = 5
+ port = mosq_test.get_port()
+ conf_file = os.path.basename(__file__).replace('.py', '.conf')
+ write_config(conf_file, port, per_listener_settings)
+
+ rc = 1
+ keepalive = 10
+ connect_packet = mosq_test.gen_connect("plugin-tick-test", keepalive=keepalive, username="readwrite", clean_session=False, proto_ver=proto_ver)
+ connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
+
+ tick_packet = mosq_test.gen_publish("topic/tick", qos=0, payload="test-message", proto_ver=proto_ver)
+
+ broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
+
+ try:
+ sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=10, port=port)
+
+ mosq_test.expect_packet(sock, "tick message", tick_packet)
+ mosq_test.expect_packet(sock, "tick message", tick_packet)
+ mosq_test.expect_packet(sock, "tick message", tick_packet)
+
+ mosq_test.do_ping(sock)
+
+ rc = 0
+ sock.close()
+ 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)
+
+do_test("false")
+do_test("true")
diff --git a/test/broker/14-dynsec-anon-group.py b/test/broker/14-dynsec-anon-group.py
index dcb7f470..a5376e4d 100755
--- a/test/broker/14-dynsec-anon-group.py
+++ b/test/broker/14-dynsec-anon-group.py
@@ -78,6 +78,15 @@ delete_anon_group_response = {'responses': [
{'command': 'deleteGroup', "error":'Deleting the anonymous group is forbidden', 'correlationData': '40'}
]}
+delete_anon_group_command = { "commands": [
+ { "command": "deleteGroup", "groupname": "anon-clients", "correlationData": "40" }
+ ]
+}
+delete_anon_group_response = {'responses': [
+ {'command': 'deleteGroup', "error":'Deleting the anonymous group is forbidden', 'correlationData': '40'}
+ ]}
+
+
rc = 1
diff --git a/test/broker/Makefile b/test/broker/Makefile
index 4248c6e2..89214cc5 100644
--- a/test/broker/Makefile
+++ b/test/broker/Makefile
@@ -203,6 +203,7 @@ endif
./09-plugin-change-id.py
./09-plugin-delayed-auth.py
./09-plugin-publish.py
+ ./09-plugin-tick.py
./09-pwfile-parse-invalid.py
10 :
diff --git a/test/broker/c/Makefile b/test/broker/c/Makefile
index e35ffce0..c65d66f6 100644
--- a/test/broker/c/Makefile
+++ b/test/broker/c/Makefile
@@ -23,6 +23,7 @@ PLUGIN_SRC = \
auth_plugin_v5.c \
auth_plugin_v5_control.c \
auth_plugin_v5_handle_message.c \
+ auth_plugin_v5_handle_tick.c \
plugin_control.c
PLUGINS = ${PLUGIN_SRC:.c=.so}
diff --git a/test/broker/c/auth_plugin_v5_handle_tick.c b/test/broker/c/auth_plugin_v5_handle_tick.c
new file mode 100644
index 00000000..d6435bb0
--- /dev/null
+++ b/test/broker/c/auth_plugin_v5_handle_tick.c
@@ -0,0 +1,38 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+static int handle_tick(int event, void *event_data, void *user_data);
+
+static mosquitto_plugin_id_t *plg_id;
+
+
+int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
+{
+ return 5;
+}
+
+int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *auth_opts, int auth_opt_count)
+{
+ plg_id = identifier;
+
+ mosquitto_callback_register(plg_id, MOSQ_EVT_TICK, handle_tick, NULL, NULL);
+
+ return MOSQ_ERR_SUCCESS;
+}
+
+int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *auth_opts, int auth_opt_count)
+{
+ mosquitto_callback_unregister(plg_id, MOSQ_EVT_TICK, handle_tick, NULL);
+
+ return MOSQ_ERR_SUCCESS;
+}
+
+int handle_tick(int event, void *event_data, void *user_data)
+{
+ mosquitto_broker_publish_copy("plugin-tick-test", "topic/tick", strlen("test-message"), "test-message", 0, false, NULL);
+ return MOSQ_ERR_SUCCESS;
+}
diff --git a/test/broker/data/CONNECT.json b/test/broker/data/CONNECT.json
index 2cc5000f..54d24fb0 100644
--- a/test/broker/data/CONNECT.json
+++ b/test/broker/data/CONNECT.json
@@ -3,6 +3,14 @@
"comment": "CONNECT TESTS ARE INCOMPLETE",
"group": "v3.1 CONNECT",
"tests": [
+ { "name": "10 ok ", "connect":false, "expect_disconnect":false, "msgs":[
+ {"type":"send", "payload":"10 0F 0006 4D5149736470 03 01 000A 0001 70", "comment":"minimal valid CONNECT"},
+ {"type":"recv", "payload":"20 02 00 00", "comment": "CONNACK"}
+ ]},
+ { "name": "14 ok ", "connect":false, "expect_disconnect":false, "msgs":[
+ {"type":"send", "payload":"14 0F 0006 4D5149736470 03 01 000A 0001 70", "comment":"CONNECT with QoS=1"},
+ {"type":"recv", "payload":"20 02 00 00", "comment": "CONNACK"}
+ ]},
{ "name": "10 proto ver 2", "connect":false, "msgs":[
{"type":"send", "payload":"10 0F 0006 4D5149736470 02 00 000A 0001 70", "comment":"CONNECT"},
{"type":"recv", "payload":"20 03 00 84 00", "comment": "CONNACK identifier rejected"}
diff --git a/test/broker/test.py b/test/broker/test.py
index d1fe29d8..f1c7d272 100755
--- a/test/broker/test.py
+++ b/test/broker/test.py
@@ -172,6 +172,7 @@ tests = [
(1, './09-plugin-change-id.py'),
(1, './09-plugin-delayed-auth.py'),
(1, './09-plugin-publish.py'),
+ (1, './09-plugin-tick.py'),
(1, './09-pwfile-parse-invalid.py'),
(2, './10-listener-mount-point.py'),
diff --git a/test/lib/08-ssl-connect-cert-auth-enc.py b/test/lib/08-ssl-connect-cert-auth-enc.py
index afc45bd0..5debedf8 100755
--- a/test/lib/08-ssl-connect-cert-auth-enc.py
+++ b/test/lib/08-ssl-connect-cert-auth-enc.py
@@ -26,9 +26,10 @@ disconnect_packet = mosq_test.gen_disconnect()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/all-ca.crt",
- keyfile=f"{ssl_dir}/server.key", certfile=f"{ssl_dir}/server.crt",
- server_side=True, cert_reqs=ssl.CERT_REQUIRED)
+context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=f"{ssl_dir}/all-ca.crt")
+context.load_cert_chain(certfile=f"{ssl_dir}/server.crt", keyfile=f"{ssl_dir}/server.key")
+context.verify_mode = ssl.CERT_REQUIRED
+ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(10)
ssock.bind(('', port))
ssock.listen(5)
diff --git a/test/lib/08-ssl-connect-cert-auth.py b/test/lib/08-ssl-connect-cert-auth.py
index 5ae510ab..7188132f 100755
--- a/test/lib/08-ssl-connect-cert-auth.py
+++ b/test/lib/08-ssl-connect-cert-auth.py
@@ -26,9 +26,10 @@ disconnect_packet = mosq_test.gen_disconnect()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/all-ca.crt",
- keyfile=f"{ssl_dir}/server.key", certfile=f"{ssl_dir}/server.crt",
- server_side=True, cert_reqs=ssl.CERT_REQUIRED)
+context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=f"{ssl_dir}/all-ca.crt")
+context.load_cert_chain(certfile=f"{ssl_dir}/server.crt", keyfile=f"{ssl_dir}/server.key")
+context.verify_mode = ssl.CERT_REQUIRED
+ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(10)
ssock.bind(('', port))
ssock.listen(5)
diff --git a/test/lib/08-ssl-connect-no-auth.py b/test/lib/08-ssl-connect-no-auth.py
index 341dc86b..4587912b 100755
--- a/test/lib/08-ssl-connect-no-auth.py
+++ b/test/lib/08-ssl-connect-no-auth.py
@@ -25,7 +25,9 @@ disconnect_packet = mosq_test.gen_disconnect()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-ssock = ssl.wrap_socket(sock, ca_certs=f"{ssl_dir}/all-ca.crt", keyfile=f"{ssl_dir}/server.key", certfile=f"{ssl_dir}/server.crt", server_side=True)
+context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=f"{ssl_dir}/all-ca.crt")
+context.load_cert_chain(certfile=f"{ssl_dir}/server.crt", keyfile=f"{ssl_dir}/server.key")
+ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(10)
ssock.bind(('', port))
ssock.listen(5)
diff --git a/test/lib/08-ssl-fake-cacert.py b/test/lib/08-ssl-fake-cacert.py
index 3d903704..e9290cef 100755
--- a/test/lib/08-ssl-fake-cacert.py
+++ b/test/lib/08-ssl-fake-cacert.py
@@ -10,14 +10,10 @@ if sys.version < '2.7':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-ssock = ssl.wrap_socket(
- sock,
- ca_certs=f"{ssl_dir}/all-ca.crt",
- keyfile=f"{ssl_dir}/server.key",
- certfile=f"{ssl_dir}/server.crt",
- server_side=True,
- cert_reqs=ssl.CERT_REQUIRED
-)
+context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=f"{ssl_dir}/all-ca.crt")
+context.load_cert_chain(certfile=f"{ssl_dir}/server.crt", keyfile=f"{ssl_dir}/server.key")
+context.verify_mode = ssl.CERT_REQUIRED
+ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(10)
ssock.bind(('', port))
ssock.listen(5)
diff --git a/test/lib/Makefile b/test/lib/Makefile
index 2405fb26..08b92c37 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -71,6 +71,8 @@ ifeq ($(WITH_TLS),yes)
./08-ssl-bad-cacert.py $@/08-ssl-bad-cacert.test
./08-ssl-connect-cert-auth-enc.py $@/08-ssl-connect-cert-auth-enc.test
./08-ssl-connect-cert-auth.py $@/08-ssl-connect-cert-auth.test
+ ./08-ssl-connect-cert-auth.py $@/08-ssl-connect-cert-auth-custom-ssl-ctx.test
+ ./08-ssl-connect-cert-auth.py $@/08-ssl-connect-cert-auth-custom-ssl-ctx-default.test
./08-ssl-connect-no-auth.py $@/08-ssl-connect-no-auth.test
endif
./09-util-topic-tokenise.py $@/09-util-topic-tokenise.test
diff --git a/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx-default.c b/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx-default.c
new file mode 100644
index 00000000..f92fcb9c
--- /dev/null
+++ b/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx-default.c
@@ -0,0 +1,67 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static int run = -1;
+
+void handle_sigint(int signal)
+{
+ run = 0;
+}
+
+void on_connect(struct mosquitto *mosq, void *obj, int rc)
+{
+ if(rc){
+ exit(1);
+ }else{
+ mosquitto_disconnect(mosq);
+ }
+}
+
+void on_disconnect(struct mosquitto *mosq, void *obj, int rc)
+{
+ run = rc;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ struct mosquitto *mosq;
+ SSL_CTX *ssl_ctx;
+ int port = atoi(argv[1]);
+
+ mosquitto_lib_init();
+
+ OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \
+ | OPENSSL_INIT_ADD_ALL_DIGESTS \
+ | OPENSSL_INIT_LOAD_CONFIG, NULL);
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+
+ mosq = mosquitto_new("08-ssl-connect-crt-auth", true, NULL);
+ if(mosq == NULL){
+ return 1;
+ }
+
+ mosquitto_int_option(mosq, MOSQ_OPT_SSL_CTX_WITH_DEFAULTS, 1);
+ mosquitto_void_option(mosq, MOSQ_OPT_SSL_CTX, ssl_ctx);
+
+ mosquitto_tls_set(mosq, "../ssl/test-root-ca.crt", "../ssl/certs", "../ssl/client.crt", "../ssl/client.key", NULL);
+ mosquitto_connect_callback_set(mosq, on_connect);
+ mosquitto_disconnect_callback_set(mosq, on_disconnect);
+
+ rc = mosquitto_connect(mosq, "localhost", port, 60);
+
+ signal(SIGINT, handle_sigint);
+ while(run == -1){
+ mosquitto_loop(mosq, -1, 1);
+ }
+ SSL_CTX_free(ssl_ctx);
+ mosquitto_destroy(mosq);
+
+ mosquitto_lib_cleanup();
+ return run;
+}
diff --git a/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx.c b/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx.c
new file mode 100644
index 00000000..e0fe94ef
--- /dev/null
+++ b/test/lib/c/08-ssl-connect-cert-auth-custom-ssl-ctx.c
@@ -0,0 +1,71 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static int run = -1;
+
+void handle_sigint(int signal)
+{
+ run = 0;
+}
+
+void on_connect(struct mosquitto *mosq, void *obj, int rc)
+{
+ if(rc){
+ exit(1);
+ }else{
+ mosquitto_disconnect(mosq);
+ }
+}
+
+void on_disconnect(struct mosquitto *mosq, void *obj, int rc)
+{
+ run = rc;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ struct mosquitto *mosq;
+ SSL_CTX *ssl_ctx;
+ int port = atoi(argv[1]);
+
+ mosquitto_lib_init();
+
+ OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \
+ | OPENSSL_INIT_ADD_ALL_DIGESTS \
+ | OPENSSL_INIT_LOAD_CONFIG, NULL);
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_use_certificate_chain_file(ssl_ctx, "../ssl/client.crt");
+ SSL_CTX_use_PrivateKey_file(ssl_ctx, "../ssl/client.key", SSL_FILETYPE_PEM);
+ SSL_CTX_load_verify_locations(ssl_ctx, "../ssl/test-root-ca.crt", "../ssl/certs");
+
+ mosq = mosquitto_new("08-ssl-connect-crt-auth", true, NULL);
+ if(mosq == NULL){
+ return 1;
+ }
+ mosquitto_tls_set(mosq, "../ssl/test-root-ca.crt", "../ssl/certs", "../ssl/client.crt", "../ssl/client.key", NULL);
+ mosquitto_connect_callback_set(mosq, on_connect);
+ mosquitto_disconnect_callback_set(mosq, on_disconnect);
+
+ mosquitto_int_option(mosq, MOSQ_OPT_SSL_CTX_WITH_DEFAULTS, 0);
+ mosquitto_void_option(mosq, MOSQ_OPT_SSL_CTX, ssl_ctx);
+
+ rc = mosquitto_connect(mosq, "localhost", port, 60);
+
+ signal(SIGINT, handle_sigint);
+ while(run == -1){
+ mosquitto_loop(mosq, -1, 1);
+ }
+ SSL_CTX_free(ssl_ctx);
+ mosquitto_destroy(mosq);
+
+ mosquitto_lib_cleanup();
+ return run;
+}
diff --git a/test/lib/c/Makefile b/test/lib/c/Makefile
index 5c9b3d04..c86e502a 100644
--- a/test/lib/c/Makefile
+++ b/test/lib/c/Makefile
@@ -1,4 +1,6 @@
R=../../..
+include ${R}/config.mk
+
.PHONY: all clean reallyclean
CFLAGS=-I${R}/include -I${R}/test -Werror
@@ -61,6 +63,13 @@ SRC = \
11-prop-send-payload-format.c \
11-prop-send-content-type.c
+ifeq ($(WITH_TLS),yes)
+SRC += \
+ 08-ssl-connect-cert-auth-custom-ssl-ctx.c \
+ 08-ssl-connect-cert-auth-custom-ssl-ctx-default.c
+LIBS += -lssl -lcrypto
+endif
+
TESTS = ${SRC:.c=.test}
all : ${TESTS}
diff --git a/test/lib/test.py b/test/lib/test.py
index 700f8c36..dc15f309 100755
--- a/test/lib/test.py
+++ b/test/lib/test.py
@@ -52,6 +52,8 @@ tests = [
(1, ['./08-ssl-bad-cacert.py', 'c/08-ssl-bad-cacert.test']),
(1, ['./08-ssl-connect-cert-auth-enc.py', 'c/08-ssl-connect-cert-auth-enc.test']),
(1, ['./08-ssl-connect-cert-auth.py', 'c/08-ssl-connect-cert-auth.test']),
+ (1, ['./08-ssl-connect-cert-auth.py', 'c/08-ssl-connect-cert-auth-custom-ssl-ctx.test']),
+ (1, ['./08-ssl-connect-cert-auth.py', 'c/08-ssl-connect-cert-auth-custom-ssl-ctx-default.test']),
(1, ['./08-ssl-connect-no-auth.py', 'c/08-ssl-connect-no-auth.test']),
(1, ['./09-util-topic-tokenise.py', 'c/09-util-topic-tokenise.test']),
diff --git a/test/unit/persist_read_stubs.c b/test/unit/persist_read_stubs.c
index 4f1da80e..6d3fbf22 100644
--- a/test/unit/persist_read_stubs.c
+++ b/test/unit/persist_read_stubs.c
@@ -250,3 +250,9 @@ void plugin_persist__queue_retain_event(struct mosquitto_base_msg *msg, int even
UNUSED(msg);
UNUSED(event);
}
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
+{
+ UNUSED(context);
+ UNUSED(expiry_time);
+ return 0;
+}
diff --git a/test/unit/persist_write_stubs.c b/test/unit/persist_write_stubs.c
index 56100e22..c661b185 100644
--- a/test/unit/persist_write_stubs.c
+++ b/test/unit/persist_write_stubs.c
@@ -222,3 +222,10 @@ void plugin_persist__queue_retain_event(struct mosquitto_base_msg *msg, int even
UNUSED(msg);
UNUSED(event);
}
+
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
+{
+ UNUSED(context);
+ UNUSED(expiry_time);
+ return 0;
+}
diff --git a/test/unit/subs_stubs.c b/test/unit/subs_stubs.c
index ed7e0570..65f5ac25 100644
--- a/test/unit/subs_stubs.c
+++ b/test/unit/subs_stubs.c
@@ -231,3 +231,9 @@ void plugin_persist__handle_subscription_delete(struct mosquitto *context, const
UNUSED(context);
UNUSED(sub);
}
+int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
+{
+ UNUSED(context);
+ UNUSED(expiry_time);
+ return 0;
+}
diff --git a/www/pages/documentation.md b/www/pages/documentation.md
index cc728dc8..a464b4a6 100644
--- a/www/pages/documentation.md
+++ b/www/pages/documentation.md
@@ -31,6 +31,15 @@
* [Using the snap package] - specific instructions on installing and configuring the Mosquitto snap package.
* [Migrating from 1.x to 2.0] - details of changes needed to migrate to version 2.0.
+# Third party
+
+These are some Mosquitto documentation hosted by third parties.
+
+* [Steve's internet guide] - a broad range of documentation and examples
+ covering Mosquitto and the Paho Python client, amongst others.
+* [docs.cedalo.com] - includes documentation for both Mosquitto and Eclipse
+ Streamsheets
+
[mosquitto]:/man/mosquitto-8.html
[mosquitto.conf]:/man/mosquitto-conf-5.html
[mosquitto_passwd]:/man/mosquitto_passwd-1.html
@@ -46,3 +55,6 @@
[Using the snap package]:/documentation/using-the-snap/
[Dynamic Security plugin]:/documentation/dynamic-security/
[Migrating from 1.x to 2.0]:/documentation/migrating-to-2-0/
+
+[Steve's internet guide]: http://www.steves-internet-guide.com/
+[docs.cedalo.com]: https://docs.cedalo.com/
diff --git a/www/pages/documentation/dynamic-security.md b/www/pages/documentation/dynamic-security.md
index 07f86830..7f16caa5 100644
--- a/www/pages/documentation/dynamic-security.md
+++ b/www/pages/documentation/dynamic-security.md
@@ -15,7 +15,7 @@
## Introduction
The Dynamic Security plugin is a Mosquitto plugin which provides role based
-authentication and access control features that can updated whilst the broker
+authentication and access control features that can be updated whilst the broker
is running, using a special topic based API.
It is supported since Mosquitto 2.0, and should be available in all
@@ -464,9 +464,9 @@ admin username and any other options once and not have to add them to the
command line every time.
mosquitto_ctrl will try to load a configuration file from a default location.
-For Windows this is at `%USER_PROFILE%\mosquitto_ctrl.conf`. For other systems,
-it will try `$XDG_CONFIG_HOME/mosquitto_ctrl.conf` or
-`$HOME/.config/mosquitto_ctrl.conf`.
+For Windows this is at `%USER_PROFILE%\mosquitto_ctrl`. For other systems,
+it will try `$XDG_CONFIG_HOME/mosquitto_ctrl` or
+`$HOME/.config/mosquitto_ctrl`.
You may override this behaviour by manually specifying an options file with
`-o `.
@@ -742,7 +742,7 @@ Where `acltype` is one of `publishClientSend`, `publishClientReceive`,
For example:
```
-mosquitto_ctrl dynsec addRoleACL clientPublishSend client/topic allow 5
+mosquitto_ctrl dynsec addRoleACL publishClientSend client/topic allow 5
```
To remove an ACL from a role using the topic filter as the key:
@@ -752,7 +752,7 @@ mosquitto_ctrl dynsec removeRoleACL dynsec removeRoleACL clientPublishSend client/topic
+mosquitto_ctrl dynsec removeRoleACL publishClientSend client/topic
```
To get information on a role:
diff --git a/www/pages/index.html b/www/pages/index.html
index 5842835c..00858e54 100644
--- a/www/pages/index.html
+++ b/www/pages/index.html
@@ -27,8 +27,8 @@
and mosquitto_sub command line MQTT clients.
Mosquitto is part of the Eclipse
- Foundation , is an iot.eclipse.org
- project and is sponsored by cedalo.com .
+ Foundation, and is an iot.eclipse.org
+ project. The development is driven by Cedalo .
@@ -37,43 +37,68 @@
-
Download
+
Download and Security
Mosquitto is highly portable and available for a wide range of
platforms. Go to the dedicated download
page to find the source or binaries for your platform.
-
Read the Change Log to find out
+
Read the Change Log to find out
about recent releases.
-
-
-
Test
-
You can have your own instance of Mosquitto running in
- minutes, but to make testing even easier, the Mosquitto Project
- runs a test server at test.mosquitto.org where
- you can test your clients in a variety of ways: plain MQTT,
- MQTT over TLS, MQTT over TLS (with client certificate ),
- MQTT over WebSockets and MQTT over WebSockets with TLS.
-
-
-
-
-
-
Security
Use the security page to find out
how to report vulnerabilities or responses to past security
issues.
+
+
+
Test
+
You can have your own instance of Mosquitto running in
+ minutes, but to make testing even easier, the Mosquitto Project
+ runs a test server at
+ test.mosquitto.org where
+ you can test your clients in a variety of ways: plain MQTT,
+ MQTT over TLS, MQTT over TLS (with
+ client certificate ),
+ MQTT over WebSockets and MQTT over WebSockets with TLS.
+
+
+
+
+
+
+
+
Support
+
Support is always available from the community channels on a
+ best effort basis. If you require commercial support,
+ Cedalo can offer support for hosted
+ or on-premise instances, consulting on the use of Mosquitto,
+ and custom development to your needs.
+
+
+
+
+
+
Related Projects
+
Paho provides MQTT
+ client library implementations in a wide variety of
+ languages.
+
Streamsheets is an
+ easy to use web based real time spreadsheet interface that can
+ be used to process incoming data from a variety of sources,
+ such as MQTT, OPC-UA, and REST. Developers and non-developers
+ can use Streamsheets to control processes and build dashboards,
+ for example. Mosquitto is a core component of Streamsheets.
+
+
+
diff --git a/www/pages/security.md b/www/pages/security.md
index 657e49ce..2f124cca 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].
+* 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**.
* August 2021: [CVE-2021-34434] Affecting versions **2.0.0** to **2.0.11**
inclusive, fixed in **2.0.12**.
* April 2021: [CVE-2021-28166] Affecting versions **2.0.0** to **2.0.9**
diff --git a/www/posts/2022/08/version-2-0-15-released.md b/www/posts/2022/08/version-2-0-15-released.md
new file mode 100644
index 00000000..2b59abce
--- /dev/null
+++ b/www/posts/2022/08/version-2-0-15-released.md
@@ -0,0 +1,101 @@
+
+
+Versions 2.0.15 of Mosquitto has been released. This is a security
+and bugfix release.
+
+# Security
+- Deleting the group configured as the anonymous group in the Dynamic Security
+ plugin, would leave a dangling pointer that could lead to a single crash.
+ This is considered a minor issue - only administrative users should have
+ access to dynsec, the impact on availability is one-off, and there is no
+ associated loss of data. It is now forbidden to delete the group configured
+ as the anonymous group.
+
+# Broker
+- Fix memory leak when a plugin modifies the topic of a message in
+ `MOSQ_EVT_MESSAGE`.
+- Fix bridge `restart_timeout` not being honoured.
+- Fix potential memory leaks if a plugin modifies the message in the
+ `MOSQ_EVT_MESSAGE` event.
+- Fix unused flags in CONNECT command being forced to be 0, which is not
+ required for MQTT v3.1. Closes [#2522].
+- Improve documentation of `persistent_client_expiration` option.
+ Closes [#2404].
+- Add clients to session expiry check list when restarting and reloading from
+ persistence. Closes [#2546].
+- Fix bridges not sending failure notification messages to the local broker if
+ the remote bridge connection fails. Closes [#2467]. Closes [#1488].
+- Fix some PUBLISH messages not being counted in $SYS stats. Closes [#2448].
+- Fix incorrect return code being sent in DISCONNECT when a client session is
+ taken over. Closes [#2607].
+- Fix confusing "out of memory" error when a client is kicked in the dynamic
+ security plugin. Closes [#2525].
+- Fix confusing error message when dynamic security config file was a
+ directory. Closes [#2520].
+- Fix bridge queued messages not being persisted when local_cleansession is
+ set to false and cleansession is set to true. Closes [#2604].
+- Dynamic security: Fix modifyClient and modifyGroup commands to not modify
+ the client/group if a new group/client being added is not valid.
+ Closes [#2598].
+- Dynamic security: Fix the plugin being able to be loaded twice. Currently
+ only a single plugin can interact with a unique $CONTROL topic. Using
+ multiple instances of the plugin would produce duplicate entries in the
+ config file. Closes [#2601]. Closes [#2470].
+- Fix case where expired messages were causing queued messages not to be
+ delivered. Closes [#2609].
+- Fix websockets not passing on the X-Forwarded-For header.
+
+# Client library
+- Fix threads library detection on Windows under cmake. Bumps the minimum
+ cmake version to 3.1, which is still ancient.
+- Fix use of `MOSQ_OPT_TLS_ENGINE` being unable to be used due to the openssl
+ ctx not being initialised until starting to connect. Closes [#2537].
+- Fix incorrect use of SSL_connect. Closes [#2594].
+- Don't set SIGPIPE to ignore, use MSG_NOSIGNAL instead. Closes [#2564].
+- Add documentation of struct mosquitto_message to header. Closes [#2561].
+- Fix documentation omission around mosquitto_reinitialise. Closes [#2489].
+- Fix use of MOSQ_OPT_SSL_CTX when used in conjunction with
+ MOSQ_OPT_SSL_CTX_DEFAULTS. Closes [#2463].
+- Fix failure to close thread in some situations. Closes [#2545].
+
+# Clients
+- Fix mosquitto_pub incorrectly reusing topic aliases when reconnecting.
+ Closes [#2494].
+
+# Apps
+- Fix `-o` not working in `mosquitto_ctrl`, and typo in related documentation.
+ Closes [#2471].
+
+
+[#1488]: https://github.com/eclipse/mosquitto/issues/1488
+[#2404]: https://github.com/eclipse/mosquitto/issues/2404
+[#2448]: https://github.com/eclipse/mosquitto/issues/2448
+[#2463]: https://github.com/eclipse/mosquitto/issues/2463
+[#2467]: https://github.com/eclipse/mosquitto/issues/2467
+[#2470]: https://github.com/eclipse/mosquitto/issues/2470
+[#2471]: https://github.com/eclipse/mosquitto/issues/2471
+[#2489]: https://github.com/eclipse/mosquitto/issues/2489
+[#2494]: https://github.com/eclipse/mosquitto/issues/2494
+[#2520]: https://github.com/eclipse/mosquitto/issues/2520
+[#2522]: https://github.com/eclipse/mosquitto/issues/2522
+[#2525]: https://github.com/eclipse/mosquitto/issues/2525
+[#2537]: https://github.com/eclipse/mosquitto/issues/2537
+[#2545]: https://github.com/eclipse/mosquitto/issues/2545
+[#2546]: https://github.com/eclipse/mosquitto/issues/2546
+[#2561]: https://github.com/eclipse/mosquitto/issues/2561
+[#2564]: https://github.com/eclipse/mosquitto/issues/2564
+[#2594]: https://github.com/eclipse/mosquitto/issues/2594
+[#2598]: https://github.com/eclipse/mosquitto/issues/2598
+[#2601]: https://github.com/eclipse/mosquitto/issues/2601
+[#2604]: https://github.com/eclipse/mosquitto/issues/2604
+[#2607]: https://github.com/eclipse/mosquitto/issues/2607
+[#2609]: https://github.com/eclipse/mosquitto/issues/2609