diff --git a/CMakeLists.txt b/CMakeLists.txt index 2775a3da..b8913c2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 "") diff --git a/ChangeLog.txt b/ChangeLog.txt index d202f4d0..26998f6d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -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 diff --git a/apps/db_dump/stubs.c b/apps/db_dump/stubs.c index f3773a6d..80ff9c69 100644 --- a/apps/db_dump/stubs.c +++ b/apps/db_dump/stubs.c @@ -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; +} diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index 3a5f4f1c..c74147b6 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 "password_mosq.h" diff --git a/apps/mosquitto_ctrl/dynsec_role.c b/apps/mosquitto_ctrl/dynsec_role.c index 14eebcaa..6f103f7c 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 01b8d580..b4d0a732 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 837470d2..45a41d86 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 5c36e5db..592d9e8c 100644 --- a/apps/mosquitto_ctrl/options.c +++ b/apps/mosquitto_ctrl/options.c @@ -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){ diff --git a/client/pub_client.c b/client/pub_client.c index 848e2c2d..7822e27f 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 3cb2cc5e..73daefdf 100644 --- a/config.mk +++ b/config.mk @@ -127,7 +127,7 @@ WITH_XTREPORT=no # Also bump lib/mosquitto.h, CMakeLists.txt, # installer/mosquitto.nsi, installer/mosquitto64.nsi -VERSION=2.0.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) diff --git a/include/mosquitto.h b/include/mosquitto.h index e514d3f6..2d34976c 100644 --- a/include/mosquitto.h +++ b/include/mosquitto.h @@ -66,7 +66,7 @@ extern "C" { #define LIBMOSQUITTO_MAJOR 2 #define LIBMOSQUITTO_MINOR 0 -#define LIBMOSQUITTO_REVISION 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 , , and . + */ 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: * , @@ -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 . + * 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/installer/mosquitto.nsi b/installer/mosquitto.nsi index eb845857..5450fae0 100644 --- a/installer/mosquitto.nsi +++ b/installer/mosquitto.nsi @@ -9,7 +9,7 @@ !define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' Name "Eclipse Mosquitto" -!define VERSION 2.0.14 +!define VERSION 2.0.15 OutFile "mosquitto-${VERSION}-install-windows-x86.exe" InstallDir "$PROGRAMFILES\mosquitto" diff --git a/installer/mosquitto64.nsi b/installer/mosquitto64.nsi index d357df4d..71d0aef9 100644 --- a/installer/mosquitto64.nsi +++ b/installer/mosquitto64.nsi @@ -9,7 +9,7 @@ !define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' Name "Eclipse Mosquitto" -!define VERSION 2.0.14 +!define VERSION 2.0.15 OutFile "mosquitto-${VERSION}-install-windows-x64.exe" !include "x64.nsh" diff --git a/lib/connect.c b/lib/connect.c index ab61b66c..dfc57fa2 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -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{ diff --git a/lib/loop.c b/lib/loop.c index 3373c186..965294f0 100644 --- a/lib/loop.c +++ b/lib/loop.c @@ -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); diff --git a/lib/mosquitto.c b/lib/mosquitto.c index 0d68d313..27a44c15 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; @@ -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 diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h index 8d066388..ac71ffbf 100644 --- a/lib/mosquitto_internal.h +++ b/lib/mosquitto_internal.h @@ -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; diff --git a/lib/net_mosq.c b/lib/net_mosq.c index 22f5a313..80d9195b 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -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 } diff --git a/lib/net_mosq.h b/lib/net_mosq.h index 37a21461..ded98760 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 29f8225f..fa7386c2 100644 --- a/lib/options.c +++ b/lib/options.c @@ -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 diff --git a/lib/packet_mosq.c b/lib/packet_mosq.c index f65769f6..80f47168 100644 --- a/lib/packet_mosq.c +++ b/lib/packet_mosq.c @@ -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 diff --git a/lib/property_mosq.c b/lib/property_mosq.c index 6249869c..71c49737 100644 --- a/lib/property_mosq.c +++ b/lib/property_mosq.c @@ -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: diff --git a/lib/strings_mosq.c b/lib/strings_mosq.c index 292a1a74..419294a6 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 fda69801..22f8c4d5 100644 --- a/lib/util_mosq.c +++ b/lib/util_mosq.c @@ -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 diff --git a/lib/util_mosq.h b/lib/util_mosq.h index 7d993442..ecc0120c 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 2c8b0955..13a18735 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("mosquitto_ctrl.1") diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index 364b9785..35e016c8 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -842,15 +842,21 @@ log_timestamp_format %Y-%m-%dT%H:%M:%S 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: @@ -954,7 +960,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 @@ -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 . + This does not apply globally, but on a per-listener basis. See also . Not reloaded on reload signal. diff --git a/mosquitto.conf b/mosquitto.conf index c46802e0..10b0406e 100644 --- a/mosquitto.conf +++ b/mosquitto.conf @@ -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 diff --git a/plugins/dynamic-security/clients.c b/plugins/dynamic-security/clients.c index a22e5cfb..78d53729 100644 --- a/plugins/dynamic-security/clients.c +++ b/plugins/dynamic-security/clients.c @@ -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; } diff --git a/plugins/dynamic-security/groups.c b/plugins/dynamic-security/groups.c index c4bdda14..f26a2ba5 100644 --- a/plugins/dynamic-security/groups.c +++ b/plugins/dynamic-security/groups.c @@ -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; } diff --git a/plugins/dynamic-security/plugin.c b/plugins/dynamic-security/plugin.c index 27c3904d..3ff1054a 100644 --- a/plugins/dynamic-security/plugin.c +++ b/plugins/dynamic-security/plugin.c @@ -25,6 +25,10 @@ Contributors: #include #include +#ifndef WIN32 +# include +#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) diff --git a/plugins/dynamic-security/roles.c b/plugins/dynamic-security/roles.c index 4d5accfc..6a393bc0 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/set-version.sh b/set-version.sh index 5f9ae4df..81d01473 100755 --- a/set-version.sh +++ b/set-version.sh @@ -2,7 +2,7 @@ MAJOR=2 MINOR=0 -REVISION=14 +REVISION=15 sed -i "s/^VERSION=.*/VERSION=${MAJOR}.${MINOR}.${REVISION}/" config.mk diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 22f38047..a330a4e5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -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. diff --git a/src/conf.c b/src/conf.c index 22b85097..a3fbc7c4 100644 --- a/src/conf.c +++ b/src/conf.c @@ -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; diff --git a/src/control.c b/src/control.c index 88c4e9da..8a70a967 100644 --- a/src/control.c +++ b/src/control.c @@ -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); diff --git a/src/database.c b/src/database.c index df9778ba..5e77a281 100644 --- a/src/database.c +++ b/src/database.c @@ -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 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; } diff --git a/src/handle_connack.c b/src/handle_connack.c index f4251fe9..e6418662 100644 --- a/src/handle_connack.c +++ b/src/handle_connack.c @@ -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"); diff --git a/src/handle_connect.c b/src/handle_connect.c index 944b9f1e..21405adf 100644 --- a/src/handle_connect.c +++ b/src/handle_connect.c @@ -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; diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 5d4f5de5..c28eaa2a 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -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); diff --git a/src/net.c b/src/net.c index ad9d53da..80d2c8d6 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 @@ -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); } diff --git a/src/persist_read.c b/src/persist_read.c index 5e7be454..107e8602 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 ccfbc29c..fb3632fe 100644 --- a/src/persist_write.c +++ b/src/persist_write.c @@ -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); diff --git a/src/plugin.c b/src/plugin.c index e0dd371a..56fad1de 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -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)); diff --git a/src/session_expiry.c b/src/session_expiry.c index 2ecceb74..98913d8a 100644 --- a/src/session_expiry.c +++ b/src/session_expiry.c @@ -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){ diff --git a/src/websockets.c b/src/websockets.c index 74e36d31..c6990e70 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -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 diff --git a/test/broker/01-connect-take-over.py b/test/broker/01-connect-take-over.py new file mode 100755 index 00000000..a275ad7e --- /dev/null +++ b/test/broker/01-connect-take-over.py @@ -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) diff --git a/test/broker/08-ssl-bridge.py b/test/broker/08-ssl-bridge.py index c48e7de0..4cbe52e1 100755 --- a/test/broker/08-ssl-bridge.py +++ b/test/broker/08-ssl-bridge.py @@ -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) diff --git a/test/broker/08-ssl-connect-cert-auth-crl.py b/test/broker/08-ssl-connect-cert-auth-crl.py index 6c348a35..0cb448ef 100755 --- a/test/broker/08-ssl-connect-cert-auth-crl.py +++ b/test/broker/08-ssl-connect-cert-auth-crl.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="../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)) diff --git a/test/broker/08-ssl-connect-cert-auth-expired.py b/test/broker/08-ssl-connect-cert-auth-expired.py index c7be02a4..41e66364 100755 --- a/test/broker/08-ssl-connect-cert-auth-expired.py +++ b/test/broker/08-ssl-connect-cert-auth-expired.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="../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)) diff --git a/test/broker/08-ssl-connect-cert-auth-revoked.py b/test/broker/08-ssl-connect-cert-auth-revoked.py index 76788bc7..92734543 100755 --- a/test/broker/08-ssl-connect-cert-auth-revoked.py +++ b/test/broker/08-ssl-connect-cert-auth-revoked.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="../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)) diff --git a/test/broker/08-ssl-connect-cert-auth-without.py b/test/broker/08-ssl-connect-cert-auth-without.py index 932dee29..3e6bf6aa 100755 --- a/test/broker/08-ssl-connect-cert-auth-without.py +++ b/test/broker/08-ssl-connect-cert-auth-without.py @@ -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)) diff --git a/test/broker/08-ssl-connect-cert-auth.py b/test/broker/08-ssl-connect-cert-auth.py index bf7c67bb..26651c3a 100755 --- a/test/broker/08-ssl-connect-cert-auth.py +++ b/test/broker/08-ssl-connect-cert-auth.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="../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)) diff --git a/test/broker/08-ssl-connect-identity.py b/test/broker/08-ssl-connect-identity.py index f1bc53d3..ba2cbb22 100755 --- a/test/broker/08-ssl-connect-identity.py +++ b/test/broker/08-ssl-connect-identity.py @@ -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)) 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 30e3e313..7a18ae61 100755 --- a/test/broker/08-ssl-connect-no-auth-wrong-ca.py +++ b/test/broker/08-ssl-connect-no-auth-wrong-ca.py @@ -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)) diff --git a/test/broker/08-ssl-connect-no-auth.py b/test/broker/08-ssl-connect-no-auth.py index 8990afeb..7b70b325 100755 --- a/test/broker/08-ssl-connect-no-auth.py +++ b/test/broker/08-ssl-connect-no-auth.py @@ -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)) diff --git a/test/broker/08-ssl-connect-no-identity.py b/test/broker/08-ssl-connect-no-identity.py index 8bbff40c..a5527d5c 100755 --- a/test/broker/08-ssl-connect-no-identity.py +++ b/test/broker/08-ssl-connect-no-identity.py @@ -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)) diff --git a/test/broker/08-ssl-hup-disconnect.py b/test/broker/08-ssl-hup-disconnect.py index b9bacd43..648556b0 100755 --- a/test/broker/08-ssl-hup-disconnect.py +++ b/test/broker/08-ssl-hup-disconnect.py @@ -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") 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 259188de..95ea3590 100755 --- a/test/broker/14-dynsec-anon-group.py +++ b/test/broker/14-dynsec-anon-group.py @@ -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() diff --git a/test/broker/Makefile b/test/broker/Makefile index d046cf92..63b9ae8f 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -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 : diff --git a/test/broker/c/Makefile b/test/broker/c/Makefile index 51fb2b79..e897e34d 100644 --- a/test/broker/c/Makefile +++ b/test/broker/c/Makefile @@ -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} 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 21fbcc50..6900e7ce 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 02 00 01", "comment": "CONNACK identifier rejected"} diff --git a/test/broker/test.py b/test/broker/test.py index 2fbb39e4..e034a83d 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -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'), diff --git a/test/lib/08-ssl-connect-cert-auth-enc.py b/test/lib/08-ssl-connect-cert-auth-enc.py index fad6b8b5..ad51e120 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="../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) diff --git a/test/lib/08-ssl-connect-cert-auth.py b/test/lib/08-ssl-connect-cert-auth.py index dba818c1..f96942ec 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="../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) diff --git a/test/lib/08-ssl-connect-no-auth.py b/test/lib/08-ssl-connect-no-auth.py index d994a5b4..17a68d1a 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="../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) diff --git a/test/lib/08-ssl-fake-cacert.py b/test/lib/08-ssl-fake-cacert.py index a1306677..dc0eb5ca 100755 --- a/test/lib/08-ssl-fake-cacert.py +++ b/test/lib/08-ssl-fake-cacert.py @@ -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) diff --git a/test/lib/Makefile b/test/lib/Makefile index 65d49ca2..6ade78d0 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -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 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 6c09e806..40cb7d15 100644 --- a/test/lib/c/Makefile +++ b/test/lib/c/Makefile @@ -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} diff --git a/test/lib/test.py b/test/lib/test.py index eb56e718..6f06c3f7 100755 --- a/test/lib/test.py +++ b/test/lib/test.py @@ -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']), diff --git a/test/unit/persist_read_stubs.c b/test/unit/persist_read_stubs.c index 2d088ac7..ec559bef 100644 --- a/test/unit/persist_read_stubs.c +++ b/test/unit/persist_read_stubs.c @@ -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; +} diff --git a/test/unit/persist_write_stubs.c b/test/unit/persist_write_stubs.c index 5cc4a597..69baffce 100644 --- a/test/unit/persist_write_stubs.c +++ b/test/unit/persist_write_stubs.c @@ -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; +} diff --git a/test/unit/subs_stubs.c b/test/unit/subs_stubs.c index 0815e4a9..ca228170 100644 --- a/test/unit/subs_stubs.c +++ b/test/unit/subs_stubs.c @@ -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; +} diff --git a/www/pages/documentation/dynamic-security.md b/www/pages/documentation/dynamic-security.md index 82f9635d..b9510800 100644 --- a/www/pages/documentation/dynamic-security.md +++ b/www/pages/documentation/dynamic-security.md @@ -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 `. 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..2a770a3a --- /dev/null +++ b/www/posts/2022/08/version-2-0-15-released.md @@ -0,0 +1,100 @@ + + +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