1
0
mirror of https://github.com/eclipse/mosquitto.git synced 2025-05-08 16:52:13 +08:00

Merge branch 'fixes'

This commit is contained in:
Roger A. Light 2022-08-16 13:13:06 +01:00
commit 9d9469cbec
81 changed files with 1108 additions and 276 deletions

View File

@ -4,13 +4,13 @@
# To configure the build options either use the CMake gui, or run the command
# line utility including the "-i" option.
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.1)
cmake_policy(SET CMP0042 NEW)
project(mosquitto)
set (VERSION 2.0.14)
set (VERSION 2.0.15)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/")
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
add_definitions (-DCMAKE -DVERSION=\"${VERSION}\")
@ -67,12 +67,9 @@ option(WITH_THREADING "Include client library threading support?" ON)
if (WITH_THREADING)
add_definitions("-DWITH_THREADING")
if (WIN32)
if (CMAKE_CL_64)
set (PTHREAD_LIBRARIES C:\\pthreads\\Pre-built.2\\lib\\x64\\pthreadVC2.lib)
else (CMAKE_CL_64)
set (PTHREAD_LIBRARIES C:\\pthreads\\Pre-built.2\\lib\\x86\\pthreadVC2.lib)
endif (CMAKE_CL_64)
set (PTHREAD_INCLUDE_DIR C:\\pthreads\\Pre-built.2\\include)
find_package(Threads REQUIRED)
set (PTHREAD_LIBRARIES Threads::Threads)
set (PTHREAD_INCLUDE_DIR "")
elseif (ANDROID)
set (PTHREAD_LIBRARIES "")
set (PTHREAD_INCLUDE_DIR "")

View File

@ -1,3 +1,69 @@
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.
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
===================
@ -25,6 +91,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

View File

@ -139,3 +139,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;
}

View File

@ -22,6 +22,10 @@ Contributors:
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "mosquitto_ctrl.h"
#include "mosquitto.h"
#include "password_mosq.h"

View File

@ -22,6 +22,10 @@ Contributors:
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "mosquitto.h"
#include "mosquitto_ctrl.h"
#include "password_mosq.h"

View File

@ -22,6 +22,10 @@ Contributors:
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "mosquitto_ctrl.h"
void ctrl_help(void)

View File

@ -24,6 +24,10 @@ Contributors:
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "lib_load.h"
#include "mosquitto.h"
#include "mosquitto_ctrl.h"

View File

@ -89,13 +89,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");
@ -531,7 +532,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){

View File

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

View File

@ -127,7 +127,7 @@ WITH_XTREPORT=no
# Also bump lib/mosquitto.h, CMakeLists.txt,
# installer/mosquitto.nsi, installer/mosquitto64.nsi
VERSION=2.0.14
VERSION=2.0.15
# Client library SO version. Bump if incompatible API/ABI changes are made.
SOVERSION=1
@ -254,10 +254,10 @@ ifeq ($(WITH_TLS),yes)
endif
ifeq ($(WITH_THREADING),yes)
LIB_LIBADD:=$(LIB_LIBADD) -lpthread
LIB_LDFLAGS:=$(LIB_LDFLAGS) -pthread
LIB_CPPFLAGS:=$(LIB_CPPFLAGS) -DWITH_THREADING
CLIENT_CPPFLAGS:=$(CLIENT_CPPFLAGS) -DWITH_THREADING
STATIC_LIB_DEPS:=$(STATIC_LIB_DEPS) -lpthread
STATIC_LIB_DEPS:=$(STATIC_LIB_DEPS) -pthread
endif
ifeq ($(WITH_SOCKS),yes)

View File

@ -66,7 +66,7 @@ extern "C" {
#define LIBMOSQUITTO_MAJOR 2
#define LIBMOSQUITTO_MINOR 0
#define LIBMOSQUITTO_REVISION 14
#define LIBMOSQUITTO_REVISION 15
/* LIBMOSQUITTO_VERSION_NUMBER looks like 1002001 for e.g. version 1.2.1. */
#define LIBMOSQUITTO_VERSION_NUMBER (LIBMOSQUITTO_MAJOR*1000000+LIBMOSQUITTO_MINOR*1000+LIBMOSQUITTO_REVISION)
@ -83,7 +83,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_AUTH_CONTINUE = -4,
MOSQ_ERR_NO_SUBSCRIBERS = -3,
@ -123,7 +124,12 @@ enum mosq_err_t {
MOSQ_ERR_ALREADY_EXISTS = 31,
};
/* Option values */
/* Enum: mosq_opt_t
*
* Client options.
*
* See <mosquitto_int_option>, <mosquitto_string_option>, and <mosquitto_void_option>.
*/
enum mosq_opt_t {
MOSQ_OPT_PROTOCOL_VERSION = 1,
MOSQ_OPT_SSL_CTX = 2,
@ -148,6 +154,24 @@ enum mosq_opt_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;
@ -322,9 +346,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:
* <mosquitto_new>, <mosquitto_destroy>
@ -1565,6 +1590,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 <mosquitto_connect>.
* 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

View File

@ -9,7 +9,7 @@
!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
Name "Eclipse Mosquitto"
!define VERSION 2.0.14
!define VERSION 2.0.15
OutFile "mosquitto-${VERSION}-install-windows-x86.exe"
InstallDir "$PROGRAMFILES\mosquitto"

View File

@ -9,7 +9,7 @@
!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
Name "Eclipse Mosquitto"
!define VERSION 2.0.14
!define VERSION 2.0.15
OutFile "mosquitto-${VERSION}-install-windows-x64.exe"
!include "x64.nsh"

View File

@ -76,6 +76,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;
}
@ -255,6 +256,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(mosq->sock == INVALID_SOCKET){
return MOSQ_ERR_NO_CONN;
}else{

View File

@ -72,12 +72,6 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets)
if(mosq->ssl){
if(mosq->want_write){
FD_SET(mosq->sock, &writefds);
}else if(mosq->want_connect){
/* Remove possible FD_SET from above, we don't want to check
* for writing if we are still connecting, unless want_write is
* definitely set. The presence of outgoing packets does not
* matter yet. */
FD_CLR(mosq->sock, &writefds);
}
}
#endif
@ -114,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
@ -167,17 +163,9 @@ int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets)
FD_SET(mosq->sock, &writefds);
}
if(mosq->sock != INVALID_SOCKET && FD_ISSET(mosq->sock, &writefds)){
#ifdef WITH_TLS
if(mosq->want_connect){
rc = net__socket_connect_tls(mosq);
if(rc) return rc;
}else
#endif
{
rc = mosquitto_loop_write(mosq, max_packets);
if(rc || mosq->sock == INVALID_SOCKET){
return rc;
}
rc = mosquitto_loop_write(mosq, max_packets);
if(rc || mosq->sock == INVALID_SOCKET){
return rc;
}
}
}
@ -254,7 +242,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;
@ -293,8 +280,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){
@ -316,8 +302,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);
@ -371,16 +356,6 @@ int mosquitto_loop_read(struct mosquitto *mosq, int max_packets)
int i;
if(max_packets < 1) return MOSQ_ERR_INVAL;
#ifdef WITH_TLS
if(mosq->want_connect){
rc = net__socket_connect_tls(mosq);
if (MOSQ_ERR_TLS == rc){
rc = mosquitto__loop_rc_handle(mosq, rc);
}
return rc;
}
#endif
pthread_mutex_lock(&mosq->msgs_out.mutex);
max_packets = mosq->msgs_out.queue_len;
pthread_mutex_unlock(&mosq->msgs_out.mutex);

View File

@ -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;
@ -167,6 +163,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;
}
}
mosq->in_packet.payload = NULL;
packet__cleanup(&mosq->in_packet);
@ -338,8 +337,6 @@ bool mosquitto_want_write(struct mosquitto *mosq)
if(mosq->ssl){
if (mosq->want_write) {
result = true;
}else if(mosq->want_connect){
result = false;
}
}
#endif

View File

@ -272,7 +272,6 @@ struct mosquitto {
enum mosquitto__keyform tls_keyform;
#endif
bool want_write;
bool want_connect;
#if defined(WITH_THREADING) && !defined(WITH_BROKER)
pthread_mutex_t callback_mutex;
pthread_mutex_t log_callback_mutex;
@ -340,6 +339,7 @@ struct mosquitto {
unsigned int reconnect_delay;
unsigned int reconnect_delay_max;
bool reconnect_exponential_backoff;
bool request_disconnect;
char threaded;
struct mosquitto__packet *out_packet_last;
mosquitto_property *connect_properties;

View File

@ -569,31 +569,7 @@ int net__socket_connect_tls(struct mosquitto *mosq)
return MOSQ_ERR_OCSP;
}
}
ret = SSL_connect(mosq->ssl);
if(ret != 1) {
err = SSL_get_error(mosq->ssl, ret);
if (err == SSL_ERROR_SYSCALL) {
mosq->want_connect = true;
return MOSQ_ERR_SUCCESS;
}
if(err == SSL_ERROR_WANT_READ){
mosq->want_connect = true;
/* We always try to read anyway */
}else if(err == SSL_ERROR_WANT_WRITE){
mosq->want_write = true;
mosq->want_connect = true;
}else{
net__print_ssl_error(mosq);
COMPAT_CLOSE(mosq->sock);
mosq->sock = INVALID_SOCKET;
net__print_ssl_error(mosq);
return MOSQ_ERR_TLS;
}
}else{
mosq->want_connect = false;
}
SSL_set_connect_state(mosq->ssl);
return MOSQ_ERR_SUCCESS;
}
#endif
@ -685,8 +661,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());
@ -1041,11 +1017,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
}

View File

@ -19,6 +19,7 @@ Contributors:
#define NET_MOSQ_H
#ifndef WIN32
# include <sys/socket.h>
# include <unistd.h>
#else
# include <winsock2.h>
@ -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)

View File

@ -284,14 +284,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

View File

@ -236,11 +236,7 @@ int packet__write(struct mosquitto *mosq)
#endif
state = mosquitto__get_state(mosq);
#if defined(WITH_TLS) && !defined(WITH_BROKER)
if(state == mosq_cs_connect_pending || mosq->want_connect){
#else
if(state == mosq_cs_connect_pending){
#endif
pthread_mutex_unlock(&mosq->current_out_packet_mutex);
return MOSQ_ERR_SUCCESS;
}
@ -546,7 +542,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

View File

@ -1208,6 +1208,7 @@ int mosquitto_property_copy_all(mosquitto_property **dest, const mosquitto_prope
}
plast = pnew;
pnew->client_generated = src->client_generated;
pnew->identifier = src->identifier;
switch(pnew->identifier){
case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR:

View File

@ -21,6 +21,10 @@ Contributors:
#include <errno.h>
#include <string.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "mosquitto.h"
#include "mqtt_protocol.h"

View File

@ -104,6 +104,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);
@ -297,3 +302,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

View File

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

View File

@ -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("mosquitto_ctrl.1")

View File

@ -842,15 +842,21 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S
<varlistentry>
<term><option>persistent_client_expiration</option> <replaceable>duration</replaceable></term>
<listitem>
<para>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.</para>
<para>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.</para>
<para>
This option allows the session of persistent clients (those with clean
session set to false) <emphasis>that are not currently connected</emphasis> 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.
</para>
<para>
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.
</para>
<para>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:</para>
@ -954,7 +960,9 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S
<listitem>
<para>Set to <replaceable>true</replaceable> 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
<replaceable>false</replaceable>.</para>
<para>Note that the MQTT v3.1.1 spec states that only QoS 1
@ -1255,6 +1263,7 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S
disconnected as not authorised when this option is
set to true. Do not use in conjunction with
<option>clientid_prefixes</option>.</para>
<para>This does not apply globally, but on a per-listener basis.</para>
<para>See also
<option>use_identity_as_username</option>.</para>
<para>Not reloaded on reload signal.</para>

View File

@ -141,14 +141,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
@ -288,6 +290,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

View File

@ -720,10 +720,12 @@ static void client__remove_all_roles(struct dynsec__client *client)
int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
{
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;
@ -746,81 +748,87 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context
return MOSQ_ERR_INVAL;
}
if(json_get_string(command, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){
if(clientid && strlen(clientid) > 0){
str = mosquitto_strdup(clientid);
if(str == NULL){
if(json_get_string(command, "clientid", &str, false) == MOSQ_ERR_SUCCESS){
have_clientid = true;
if(str && strlen(str) > 0){
clientid = mosquitto_strdup(str);
if(clientid == NULL){
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
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(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){
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
mosquitto_kick_client_by_username(username, false);
return MOSQ_ERR_NOMEM;
}
have_password = true;
}
}
if(json_get_string(command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){
str = mosquitto_strdup(text_name);
if(str == NULL){
if(json_get_string(command, "textname", &str, false) == MOSQ_ERR_SUCCESS){
have_text_name = true;
text_name = mosquitto_strdup(str);
if(text_name == NULL){
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
mosquitto_kick_client_by_username(username, false);
return MOSQ_ERR_NOMEM;
rc = MOSQ_ERR_NOMEM;
goto error;
}
mosquitto_free(client->text_name);
client->text_name = str;
}
if(json_get_string(command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){
str = mosquitto_strdup(text_description);
if(str == NULL){
if(json_get_string(command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){
have_text_description = true;
text_description = mosquitto_strdup(str);
if(text_description == NULL){
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
mosquitto_kick_client_by_username(username, false);
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(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){
dynsec__command_reply(j_responses, context, "modifyClient", "Role not found", correlation_data);
dynsec_rolelist__cleanup(&rolelist);
mosquitto_kick_client_by_username(username, false);
return MOSQ_ERR_INVAL;
rc = MOSQ_ERR_INVAL;
goto error;
}else{
if(rc == MOSQ_ERR_INVAL){
dynsec__command_reply(j_responses, context, "modifyClient", "'roles' not an array or missing/invalid rolename", correlation_data);
}else{
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
}
dynsec_rolelist__cleanup(&rolelist);
mosquitto_kick_client_by_username(username, false);
return MOSQ_ERR_INVAL;
rc = MOSQ_ERR_INVAL;
goto error;
}
j_groups = cJSON_GetObjectItem(command, "groups");
if(j_groups && cJSON_IsArray(j_groups)){
dynsec__remove_client_from_all_groups(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(jtmp->valuestring);
if(group == NULL){
dynsec__command_reply(j_responses, context, "modifyClient", "'groups' contains an object with a 'groupname' that does not exist", correlation_data);
rc = MOSQ_ERR_INVAL;
goto error;
}
}else{
dynsec__command_reply(j_responses, context, "modifyClient", "'groups' contains an object with an invalid 'groupname'", correlation_data);
rc = MOSQ_ERR_INVAL;
goto error;
}
}
}
dynsec__remove_client_from_all_groups(username);
cJSON_ArrayForEach(j_group, j_groups){
if(cJSON_IsObject(j_group)){
jtmp = cJSON_GetObjectItem(j_group, "groupname");
@ -832,6 +840,44 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context
}
}
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){
dynsec__command_reply(j_responses, context, "modifyClient", "Internal error", correlation_data);
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();
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();
dynsec__command_reply(j_responses, context, "modifyClient", NULL, correlation_data);
@ -843,6 +889,12 @@ int dynsec_clients__process_modify(cJSON *j_responses, struct mosquitto *context
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;
}

View File

@ -466,6 +466,11 @@ int dynsec_groups__process_delete(cJSON *j_responses, struct mosquitto *context,
group = dynsec_groups__find(groupname);
if(group){
if(group == dynsec_anonymous_group){
dynsec__command_reply(j_responses, context, "deleteGroup", "Deleting the anonymous group is forbidden", correlation_data);
return MOSQ_ERR_INVAL;
}
/* Enforce any changes */
group__kick_all(group);
@ -911,10 +916,12 @@ int dynsec_groups__process_remove_role(cJSON *j_responses, struct mosquitto *con
int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data)
{
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;
@ -936,52 +943,73 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context,
return MOSQ_ERR_INVAL;
}
if(json_get_string(command, "textname", &text_name, false) == MOSQ_ERR_SUCCESS){
str = mosquitto_strdup(text_name);
if(str == NULL){
if(json_get_string(command, "textname", &str, false) == MOSQ_ERR_SUCCESS){
have_text_name = true;
text_name = mosquitto_strdup(str);
if(text_name == NULL){
dynsec__command_reply(j_responses, context, "modifyGroup", "Internal error", correlation_data);
return MOSQ_ERR_NOMEM;
rc = MOSQ_ERR_NOMEM;
goto error;
}
mosquitto_free(group->text_name);
group->text_name = str;
}
if(json_get_string(command, "textdescription", &text_description, false) == MOSQ_ERR_SUCCESS){
str = mosquitto_strdup(text_description);
if(str == NULL){
if(json_get_string(command, "textdescription", &str, false) == MOSQ_ERR_SUCCESS){
have_text_description = true;
text_description = mosquitto_strdup(str);
if(text_description == NULL){
dynsec__command_reply(j_responses, context, "modifyGroup", "Internal error", correlation_data);
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(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){
dynsec__command_reply(j_responses, context, "modifyGroup", "Role not found", correlation_data);
dynsec_rolelist__cleanup(&rolelist);
group__kick_all(group);
return MOSQ_ERR_INVAL;
rc = MOSQ_ERR_INVAL;
goto error;
}else{
if(rc == MOSQ_ERR_INVAL){
dynsec__command_reply(j_responses, context, "modifyGroup", "'roles' not an array or missing/invalid rolename", correlation_data);
}else{
dynsec__command_reply(j_responses, context, "modifyGroup", "Internal error", correlation_data);
}
dynsec_rolelist__cleanup(&rolelist);
group__kick_all(group);
return MOSQ_ERR_INVAL;
rc = MOSQ_ERR_INVAL;
goto error;
}
j_clients = cJSON_GetObjectItem(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(jtmp->valuestring);
if(client == NULL){
dynsec__command_reply(j_responses, context, "modifyGroup", "'clients' contains an object with a 'username' that does not exist", correlation_data);
rc = MOSQ_ERR_INVAL;
goto error;
}
}else{
dynsec__command_reply(j_responses, context, "modifyGroup", "'clients' contains an object with an invalid 'username'", correlation_data);
rc = MOSQ_ERR_INVAL;
goto error;
}
}
}
/* Kick all clients in the *current* group */
group__kick_all(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");
@ -993,11 +1021,28 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context,
}
}
/* 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();
dynsec__command_reply(j_responses, context, "modifyGroup", NULL, correlation_data);
/* Enforce any changes */
/* Enforce any changes - kick any clients in the *new* group */
group__kick_all(group);
admin_clientid = mosquitto_client_id(context);
@ -1006,6 +1051,17 @@ int dynsec_groups__process_modify(cJSON *j_responses, struct mosquitto *context,
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;
}

View File

@ -25,6 +25,10 @@ Contributors:
#include <string.h>
#include <sys/stat.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "json_help.h"
#include "mosquitto.h"
#include "mosquitto_broker.h"
@ -354,18 +358,25 @@ static int dynsec__config_load(void)
cJSON *tree;
/* Load from file */
errno = 0;
fptr = fopen(config_file, "rb");
if(fptr == NULL){
mosquitto_log_printf(MOSQ_LOG_ERR, "Error loading Dynamic security plugin config: File is not readable - check permissions.\n");
return 1;
return MOSQ_ERR_ERRNO;
}
#ifndef WIN32
if(errno == ENOTDIR || errno == EISDIR){
mosquitto_log_printf(MOSQ_LOG_ERR, "Error loading Dynamic security plugin config: Config is not a file.\n");
return MOSQ_ERR_ERRNO;
}
#endif
fseek(fptr, 0, SEEK_END);
flen_l = ftell(fptr);
if(flen_l < 0){
mosquitto_log_printf(MOSQ_LOG_ERR, "Error loading Dynamic security plugin config: %s\n", strerror(errno));
fclose(fptr);
return 1;
return MOSQ_ERR_ERRNO;
}else if(flen_l == 0){
fclose(fptr);
return 0;
@ -376,13 +387,13 @@ static int dynsec__config_load(void)
if(json_str == NULL){
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
fclose(fptr);
return 1;
return MOSQ_ERR_NOMEM;
}
if(fread(json_str, 1, flen, fptr) != flen){
mosquitto_log_printf(MOSQ_LOG_WARNING, "Error loading Dynamic security plugin config: Unable to read file contents.\n");
mosquitto_free(json_str);
fclose(fptr);
return 1;
return MOSQ_ERR_ERRNO;
}
fclose(fptr);
@ -390,7 +401,7 @@ static int dynsec__config_load(void)
mosquitto_free(json_str);
if(tree == NULL){
mosquitto_log_printf(MOSQ_LOG_ERR, "Error loading Dynamic security plugin config: File is not valid JSON.\n");
return 1;
return MOSQ_ERR_INVAL;
}
if(dynsec__general_config_load(tree)
@ -400,7 +411,7 @@ static int dynsec__config_load(void)
){
cJSON_Delete(tree);
return 1;
return MOSQ_ERR_NOMEM;
}
cJSON_Delete(tree);
@ -471,6 +482,7 @@ void dynsec__config_save(void)
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);
@ -491,11 +503,46 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, s
plg_id = identifier;
dynsec__config_load();
mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", NULL);
mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, dynsec_auth__basic_auth_callback, NULL, NULL);
mosquitto_callback_register(plg_id, MOSQ_EVT_ACL_CHECK, dynsec__acl_check_callback, NULL, NULL);
rc = mosquitto_callback_register(plg_id, MOSQ_EVT_CONTROL, dynsec_control_callback, "$CONTROL/dynamic-security/v1", NULL);
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, NULL);
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, NULL);
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;
}
return MOSQ_ERR_SUCCESS;
error:
mosquitto_free(config_file);
config_file = NULL;
return rc;
}
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *options, int option_count)

View File

@ -24,6 +24,10 @@ Contributors:
#include <uthash.h>
#include <utlist.h>
#ifndef WIN32
# include <strings.h>
#endif
#include "dynamic_security.h"
#include "json_help.h"
#include "mosquitto.h"

View File

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

View File

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

View File

@ -2,7 +2,7 @@
MAJOR=2
MINOR=0
REVISION=14
REVISION=15
sed -i "s/^VERSION=.*/VERSION=${MAJOR}.${MINOR}.${REVISION}/" config.mk

View File

@ -1,5 +1,5 @@
name: mosquitto
version: 2.0.14
version: 2.0.15
summary: Eclipse Mosquitto MQTT broker
description: This is a message broker that supports version 5.0, 3.1.1, and 3.1 of the MQTT
protocol.

View File

@ -1897,6 +1897,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;

View File

@ -65,9 +65,9 @@ int control__process(struct mosquitto *context, struct mosquitto_msg_store *stor
}
if(stored->qos == 1){
if(send__puback(context, stored->source_mid, MQTT_RC_SUCCESS, properties)) rc = 1;
rc = send__puback(context, stored->source_mid, MQTT_RC_SUCCESS, properties);
}else if(stored->qos == 2){
if(send__pubrec(context, stored->source_mid, MQTT_RC_SUCCESS, properties)) rc = 1;
rc = send__pubrec(context, stored->source_mid, MQTT_RC_SUCCESS, properties);
}
mosquitto_property_free_all(&properties);

View File

@ -339,7 +339,7 @@ void db__msg_store_compact(void)
}
static void db__message_remove(struct mosquitto_msg_data *msg_data, struct mosquitto_client_msg *item)
static void db__message_remove_from_inflight(struct mosquitto_msg_data *msg_data, struct mosquitto_client_msg *item)
{
if(!msg_data || !item){
return;
@ -356,6 +356,22 @@ static void db__message_remove(struct mosquitto_msg_data *msg_data, struct mosqu
}
static void db__message_remove_from_queued(struct mosquitto_msg_data *msg_data, struct mosquitto_client_msg *item)
{
if(!msg_data || !item){
return;
}
DL_DELETE(msg_data->queued, item);
if(item->store){
db__msg_store_ref_dec(&item->store);
}
mosquitto_property_free_all(&item->properties);
mosquitto__free(item);
}
void db__message_dequeue_first(struct mosquitto *context, struct mosquitto_msg_data *msg_data)
{
struct mosquitto_client_msg *msg;
@ -390,7 +406,7 @@ int db__message_delete_outgoing(struct mosquitto *context, uint16_t mid, enum mo
return MOSQ_ERR_PROTOCOL;
}
msg_index--;
db__message_remove(&context->msgs_out, tail);
db__message_remove_from_inflight(&context->msgs_out, tail);
break;
}
}
@ -894,7 +910,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context)
if(msg->qos != 2){
/* Anything <QoS 2 can be completely retried by the client at
* no harm. */
db__message_remove(&context->msgs_in, msg);
db__message_remove_from_inflight(&context->msgs_in, msg);
}else{
/* Message state can be preserved here because it should match
* whatever the client has got. */
@ -950,7 +966,7 @@ int db__message_remove_incoming(struct mosquitto* context, uint16_t mid)
if(tail->store->qos != 2){
return MOSQ_ERR_PROTOCOL;
}
db__message_remove(&context->msgs_in, tail);
db__message_remove_from_inflight(&context->msgs_in, tail);
return MOSQ_ERR_SUCCESS;
}
}
@ -986,12 +1002,12 @@ int db__message_release_incoming(struct mosquitto *context, uint16_t mid)
* keep resending it. That means we don't send it to other
* clients. */
if(topic == NULL){
db__message_remove(&context->msgs_in, tail);
db__message_remove_from_inflight(&context->msgs_in, tail);
deleted = true;
}else{
rc = sub__messages_queue(source_id, topic, 2, retain, &tail->store);
if(rc == MOSQ_ERR_SUCCESS || rc == MOSQ_ERR_NO_SUBSCRIBERS){
db__message_remove(&context->msgs_in, tail);
db__message_remove_from_inflight(&context->msgs_in, tail);
deleted = true;
}else{
return 1;
@ -1021,6 +1037,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->store->message_expiry_time && db.now_real_s > msg->store->message_expiry_time){
if(msg->qos > 0){
util__increment_send_quota(context);
}
db__message_remove_from_inflight(&context->msgs_out, msg);
}
}
DL_FOREACH_SAFE(context->msgs_out.queued, msg, tmp){
if(msg->store->message_expiry_time && db.now_real_s > msg->store->message_expiry_time){
db__message_remove_from_queued(&context->msgs_out, msg);
}
}
DL_FOREACH_SAFE(context->msgs_in.inflight, msg, tmp){
if(msg->store->message_expiry_time && db.now_real_s > msg->store->message_expiry_time){
if(msg->qos > 0){
util__increment_receive_quota(context);
}
db__message_remove_from_inflight(&context->msgs_in, msg);
}
}
DL_FOREACH_SAFE(context->msgs_in.queued, msg, tmp){
if(msg->store->message_expiry_time && db.now_real_s > msg->store->message_expiry_time){
db__message_remove_from_queued(&context->msgs_in, msg);
}
}
}
static int db__message_write_inflight_out_single(struct mosquitto *context, struct mosquitto_client_msg *msg)
{
mosquitto_property *cmsg_props = NULL, *store_props = NULL;
@ -1041,7 +1091,7 @@ static int db__message_write_inflight_out_single(struct mosquitto *context, stru
if(msg->direction == mosq_md_out && msg->qos > 0){
util__increment_send_quota(context);
}
db__message_remove(&context->msgs_out, msg);
db__message_remove_from_inflight(&context->msgs_out, msg);
return MOSQ_ERR_SUCCESS;
}else{
expiry_interval = (uint32_t)(msg->store->message_expiry_time - db.now_real_s);
@ -1061,7 +1111,7 @@ static int db__message_write_inflight_out_single(struct mosquitto *context, stru
case mosq_ms_publish_qos0:
rc = send__publish(context, mid, topic, payloadlen, payload, qos, retain, retries, cmsg_props, store_props, expiry_interval);
if(rc == MOSQ_ERR_SUCCESS || rc == MOSQ_ERR_OVERSIZE_PACKET){
db__message_remove(&context->msgs_out, msg);
db__message_remove_from_inflight(&context->msgs_out, msg);
}else{
return rc;
}
@ -1074,7 +1124,7 @@ static int db__message_write_inflight_out_single(struct mosquitto *context, stru
msg->dup = 1; /* Any retry attempts are a duplicate. */
msg->state = mosq_ms_wait_for_puback;
}else if(rc == MOSQ_ERR_OVERSIZE_PACKET){
db__message_remove(&context->msgs_out, msg);
db__message_remove_from_inflight(&context->msgs_out, msg);
}else{
return rc;
}
@ -1087,7 +1137,7 @@ static int db__message_write_inflight_out_single(struct mosquitto *context, stru
msg->dup = 1; /* Any retry attempts are a duplicate. */
msg->state = mosq_ms_wait_for_pubrec;
}else if(rc == MOSQ_ERR_OVERSIZE_PACKET){
db__message_remove(&context->msgs_out, msg);
db__message_remove_from_inflight(&context->msgs_out, msg);
}else{
return rc;
}
@ -1188,7 +1238,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;
}

View File

@ -163,7 +163,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");

View File

@ -205,6 +205,10 @@ int connect__on_authorised(struct mosquitto *context, void *auth_data_out, uint1
found_context->clean_start = true;
found_context->session_expiry_interval = 0;
mosquitto__set_state(found_context, mosq_cs_duplicate);
if(found_context->protocol == mosq_p_mqtt5){
send__disconnect(found_context, MQTT_RC_SESSION_TAKEN_OVER, NULL);
}
do_disconnect(found_context, MOSQ_ERR_SUCCESS);
}
@ -314,6 +318,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);
@ -458,9 +463,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
@ -528,6 +530,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;

View File

@ -670,6 +670,7 @@ int db__message_write_queued_out(struct mosquitto *context);
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);
void db__expire_all_messages(struct mosquitto *context);
/* ============================================================
* Subscription functions
@ -818,6 +819,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);

View File

@ -19,15 +19,16 @@ Contributors:
#include "config.h"
#ifndef WIN32
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <ifaddrs.h>
# include <arpa/inet.h>
# include <ifaddrs.h>
# include <netdb.h>
# include <netinet/tcp.h>
# include <strings.h>
# include <sys/socket.h>
# include <unistd.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
# include <winsock2.h>
# include <ws2tcpip.h>
#endif
#include <assert.h>
@ -36,7 +37,7 @@ Contributors:
#include <stdio.h>
#include <string.h>
#ifdef WITH_WRAP
#include <tcpd.h>
# include <tcpd.h>
#endif
#ifdef HAVE_NETINET_IN_H
@ -49,7 +50,7 @@ Contributors:
#endif
#ifdef __QNX__
#include <net/netbyte.h>
# include <net/netbyte.h>
#endif
#include "mosquitto_broker_internal.h"
@ -59,8 +60,8 @@ Contributors:
#include "util_mosq.h"
#ifdef WITH_TLS
#include "tls_mosq.h"
#include <openssl/err.h>
# include "tls_mosq.h"
# include <openssl/err.h>
static int tls_ex_index_context = -1;
static int tls_ex_index_listener = -1;
#endif
@ -569,7 +570,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){
@ -582,7 +583,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){
@ -599,11 +600,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);
}

View File

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

View File

@ -167,8 +167,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);

View File

@ -157,19 +157,29 @@ int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store
DL_FOREACH(opts->plugin_callbacks.message, cb_base){
rc = cb_base->cb(MOSQ_EVT_MESSAGE, &event_data, cb_base->userdata);
if(stored->topic != event_data.topic){
mosquitto__free(stored->topic);
stored->topic = event_data.topic;
}
if(stored->payload != event_data.payload){
mosquitto__free(stored->payload);
stored->payload = event_data.payload;
stored->payloadlen = event_data.payloadlen;
}
if(stored->properties != event_data.properties){
mosquitto_property_free_all(&stored->properties);
stored->properties = event_data.properties;
}
if(rc != MOSQ_ERR_SUCCESS){
break;
}
}
stored->topic = event_data.topic;
if(stored->payload != event_data.payload){
mosquitto__free(stored->payload);
stored->payload = event_data.payload;
stored->payloadlen = event_data.payloadlen;
}
stored->retain = event_data.retain;
stored->properties = event_data.properties;
return rc;
}
@ -180,10 +190,18 @@ void plugin__handle_tick(void)
struct mosquitto_evt_tick event_data;
struct mosquitto__callback *cb_base;
struct mosquitto__security_options *opts;
int i;
/* FIXME - set now_s and now_ns to avoid need for multiple time lookups */
if(db.config->per_listener_settings){
/* FIXME - iterate over all listeners */
for(i=0; i < db.config->listener_count; i++){
opts = &db.config->listeners[i].security_options;
memset(&event_data, 0, sizeof(event_data));
DL_FOREACH(opts->plugin_callbacks.tick, cb_base){
cb_base->cb(MOSQ_EVT_TICK, &event_data, cb_base->userdata);
}
}
}else{
opts = &db.config->security_options;
memset(&event_data, 0, sizeof(event_data));

View File

@ -82,6 +82,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){

View File

@ -261,7 +261,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
@ -356,7 +356,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

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python3
# MQTT v5 session takeover test
from mosq_test_helper import *
port = mosq_test.get_port()
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port)
try:
rc = 1
connect_packet = mosq_test.gen_connect("take-over", proto_ver=5)
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=5)
disconnect_packet = mosq_test.gen_disconnect(reason_code=mqtt5_rc.MQTT_RC_SESSION_TAKEN_OVER, proto_ver=5)
sock1 = mosq_test.do_client_connect(connect_packet, connack_packet, port=port)
sock2 = mosq_test.do_client_connect(connect_packet, connack_packet, port=port)
mosq_test.expect_packet(sock1, "disconnect", disconnect_packet)
mosq_test.do_ping(sock2)
sock2.close()
sock1.close()
rc = 0
except mosq_test.TestError:
pass
except Exception as e:
print(e)
finally:
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
exit(rc)

View File

@ -34,7 +34,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/all-ca.crt", keyfile="../ssl/server.key", certfile="../ssl/server.crt", server_side=True)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile="../ssl/all-ca.crt")
context.load_cert_chain(certfile="../ssl/server.crt", keyfile="../ssl/server.key")
ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(20)
ssock.bind(('', port1))
ssock.listen(5)

View File

@ -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="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client.crt", keyfile="../ssl/client.key")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))

View File

@ -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="../ssl/test-root-ca.crt", certfile="../ssl/client-expired.crt", keyfile="../ssl/client-expired.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client-expired.crt", keyfile="../ssl/client-expired.key")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))

View File

@ -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="../ssl/test-root-ca.crt", certfile="../ssl/client-revoked.crt", keyfile="../ssl/client-revoked.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client-revoked.crt", keyfile="../ssl/client-revoked.key")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))

View File

@ -28,7 +28,8 @@ connect_packet = mosq_test.gen_connect("connect-cert-test", keepalive=keepalive)
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="../ssl/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))

View File

@ -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="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client.crt", keyfile="../ssl/client.key")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))

View File

@ -33,7 +33,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="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client.crt", keyfile="../ssl/client.key")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))

View File

@ -29,7 +29,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="../ssl/test-alt-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-alt-ca.crt")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
try:
ssock.connect(("localhost", port1))

View File

@ -32,7 +32,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="../ssl/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))

View File

@ -32,7 +32,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="../ssl/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
ssock = context.wrap_socket(sock, server_hostname="localhost")
ssock.settimeout(20)
ssock.connect(("localhost", port1))

View File

@ -43,7 +43,9 @@ def do_test(option):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile="../ssl/test-root-ca.crt")
context.load_cert_chain(certfile="../ssl/client.crt", keyfile="../ssl/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")

52
test/broker/09-plugin-tick.py Executable file
View File

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

View File

@ -71,6 +71,15 @@ create_role_apply_response = {'responses': [
]}
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
keepalive = 10
@ -136,6 +145,9 @@ try:
csock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=5, port=port)
mosq_test.do_send_receive(csock, subscribe_packet, suback_packet_success, "suback 3")
# Try to delete anon group, this should fail
command_check(sock, delete_anon_group_command, delete_anon_group_response)
rc = 0
sock.close()

View File

@ -28,6 +28,7 @@ msg_sequence_test:
./01-connect-disconnect-v5.py
./01-connect-max-connections.py
./01-connect-max-keepalive.py
./01-connect-take-over.py
./01-connect-uname-no-password-denied.py
./01-connect-uname-or-anon.py
./01-connect-uname-password-denied-no-will.py
@ -183,6 +184,7 @@ endif
./09-plugin-auth-v2-unpwd-fail.py
./09-plugin-auth-v2-unpwd-success.py
./09-plugin-publish.py
./09-plugin-tick.py
./09-pwfile-parse-invalid.py
10 :

View File

@ -18,6 +18,7 @@ PLUGIN_SRC = \
auth_plugin_v4.c \
auth_plugin_v5.c \
auth_plugin_v5_handle_message.c \
auth_plugin_v5_handle_tick.c \
plugin_control.c
PLUGINS = ${PLUGIN_SRC:.c=.so}

View File

@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mosquitto.h>
#include <mosquitto_broker.h>
#include <mosquitto_plugin.h>
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;
}

View File

@ -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 02 00 01", "comment": "CONNACK identifier rejected"}

View File

@ -10,6 +10,7 @@ tests = [
(1, './01-connect-disconnect-v5.py'),
(1, './01-connect-max-connections.py'),
(1, './01-connect-max-keepalive.py'),
(1, './01-connect-take-over.py'),
(1, './01-connect-uname-no-password-denied.py'),
(1, './01-connect-uname-or-anon.py'),
(1, './01-connect-uname-password-denied-no-will.py'),
@ -153,6 +154,7 @@ tests = [
(1, './09-plugin-auth-v2-unpwd-fail.py'),
(1, './09-plugin-auth-v2-unpwd-success.py'),
(1, './09-plugin-publish.py'),
(1, './09-plugin-tick.py'),
(1, './09-pwfile-parse-invalid.py'),
(2, './10-listener-mount-point.py'),

View File

@ -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="../ssl/all-ca.crt",
keyfile="../ssl/server.key", certfile="../ssl/server.crt",
server_side=True, cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile="../ssl/all-ca.crt")
context.load_cert_chain(certfile="../ssl/server.crt", keyfile="../ssl/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)

View File

@ -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="../ssl/all-ca.crt",
keyfile="../ssl/server.key", certfile="../ssl/server.crt",
server_side=True, cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile="../ssl/all-ca.crt")
context.load_cert_chain(certfile="../ssl/server.crt", keyfile="../ssl/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)

View File

@ -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="../ssl/all-ca.crt", keyfile="../ssl/server.key", certfile="../ssl/server.crt", server_side=True)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile="../ssl/all-ca.crt")
context.load_cert_chain(certfile="../ssl/server.crt", keyfile="../ssl/server.key")
ssock = context.wrap_socket(sock, server_side=True)
ssock.settimeout(10)
ssock.bind(('', port))
ssock.listen(5)

View File

@ -10,9 +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="../ssl/all-ca.crt",
keyfile="../ssl/server.key", certfile="../ssl/server.crt",
server_side=True, cert_reqs=ssl.CERT_REQUIRED)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile="../ssl/all-ca.crt")
context.load_cert_chain(certfile="../ssl/server.crt", keyfile="../ssl/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)

View File

@ -65,6 +65,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

View File

@ -0,0 +1,67 @@
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <mosquitto.h>
#include <openssl/ssl.h>
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;
}

View File

@ -0,0 +1,71 @@
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <mosquitto.h>
#include <openssl/ssl.h>
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;
}

View File

@ -1,3 +1,5 @@
include ../../../config.mk
.PHONY: all clean reallyclean
CFLAGS=-I../../../include -Werror
@ -55,6 +57,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}

View File

@ -48,6 +48,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']),

View File

@ -205,3 +205,10 @@ void context__add_to_by_id(struct mosquitto *context)
HASH_ADD_KEYPTR(hh_id, db.contexts_by_id, context->id, strlen(context->id), context);
}
}
int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
{
UNUSED(context);
UNUSED(expiry_time);
return 0;
}

View File

@ -121,3 +121,10 @@ void context__add_to_by_id(struct mosquitto *context)
HASH_ADD_KEYPTR(hh_id, db.contexts_by_id, context->id, strlen(context->id), context);
}
}
int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
{
UNUSED(context);
UNUSED(expiry_time);
return 0;
}

View File

@ -219,3 +219,10 @@ void util__increment_send_quota(struct mosquitto *mosq)
{
mosq->msgs_out.inflight_quota++;
}
int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time)
{
UNUSED(context);
UNUSED(expiry_time);
return 0;
}

View File

@ -389,9 +389,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 <path to options file>`.

View File

@ -0,0 +1,100 @@
<!--
.. title: Version 2.0.15 released.
.. slug: version-2-0-15-released
.. date: 2022-08-16 12:57:38 UTC+1
.. tags: Releases
.. category:
.. link:
.. description:
.. type: text
-->
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].
# 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