Merge branch 'fixes'
This commit is contained in:
commit
c03c6b765e
@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.0)
|
||||
cmake_policy(SET CMP0042 NEW)
|
||||
|
||||
project(mosquitto)
|
||||
set (VERSION 2.0.8)
|
||||
set (VERSION 2.0.9)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/")
|
||||
|
||||
|
@ -1,3 +1,39 @@
|
||||
2.0.9 - 2021-03-11
|
||||
==================
|
||||
|
||||
Security:
|
||||
- If an empty or invalid CA file was provided to the client library for
|
||||
verifying the remote broker, then the initial connection would fail but
|
||||
subsequent connections would succeed without verifying the remote broker
|
||||
certificate. Closes #2130.
|
||||
- If an empty or invalid CA file was provided to the broker for verifying the
|
||||
remote broker for an outgoing bridge connection then the initial connection
|
||||
would fail but subsequent connections would succeed without verifying the
|
||||
remote broker certificate. Closes #2130.
|
||||
|
||||
Broker:
|
||||
- Fix encrypted bridge connections incorrectly connecting when `bridge_cafile`
|
||||
is empty or invalid. Closes #2130.
|
||||
- Fix `tls_version` behaviour not matching documentation. It was setting the
|
||||
exact TLS version to use, not the minimium TLS version to use. Closes #2110.
|
||||
- Fix messages to `$` prefixed topics being rejected. Closes #2111.
|
||||
- Fix QoS 0 messages not being delivered when max_queued_bytes was configured.
|
||||
Closes #2123.
|
||||
- Fix bridge increasing backoff calculation.
|
||||
- Improve handling of invalid combinations of listener address and bind
|
||||
interface configurations. Closes #2081.
|
||||
- Fix `max_keepalive` option not applying to clients connecting with keepalive
|
||||
set to 0. Closes #2117.
|
||||
|
||||
Client library:
|
||||
- Fix encrypted connections incorrectly connecting when the CA file passed to
|
||||
`mosquitto_tls_set()` is empty or invalid. Closes #2130.
|
||||
- Fix connections retrying very rapidly in some situations.
|
||||
|
||||
Build:
|
||||
- Fix cmake epoll detection.
|
||||
|
||||
|
||||
2.0.8 - 2021-02-25
|
||||
==================
|
||||
|
||||
|
@ -127,7 +127,7 @@ WITH_XTREPORT=no
|
||||
|
||||
# Also bump lib/mosquitto.h, CMakeLists.txt,
|
||||
# installer/mosquitto.nsi, installer/mosquitto64.nsi
|
||||
VERSION=2.0.8
|
||||
VERSION=2.0.9
|
||||
|
||||
# Client library SO version. Bump if incompatible API/ABI changes are made.
|
||||
SOVERSION=1
|
||||
|
@ -66,7 +66,7 @@ extern "C" {
|
||||
|
||||
#define LIBMOSQUITTO_MAJOR 2
|
||||
#define LIBMOSQUITTO_MINOR 0
|
||||
#define LIBMOSQUITTO_REVISION 8
|
||||
#define LIBMOSQUITTO_REVISION 9
|
||||
/* 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)
|
||||
|
||||
@ -1604,40 +1604,6 @@ libmosq_EXPORT int mosquitto_string_option(struct mosquitto *mosq, enum mosq_opt
|
||||
*/
|
||||
libmosq_EXPORT int mosquitto_void_option(struct mosquitto *mosq, enum mosq_opt_t option, void *value);
|
||||
|
||||
/*
|
||||
* Function: mosquitto_string_option
|
||||
*
|
||||
* Used to set const char* options for the client.
|
||||
*
|
||||
* Parameters:
|
||||
* mosq - a valid mosquitto instance.
|
||||
* option - the option to set.
|
||||
* value - the option specific value.
|
||||
*
|
||||
* Options:
|
||||
* MOSQ_OPT_TLS_ENGINE
|
||||
* Configure the client for TLS Engine support. Pass a TLS Engine ID
|
||||
* to be used when creating TLS connections.
|
||||
* Must be set before <mosquitto_connect>.
|
||||
* MOSQ_OPT_TLS_KEYFORM
|
||||
* Configure the client to treat the keyfile differently depending
|
||||
* on its type. Must be set before <mosquitto_connect>.
|
||||
* Set as either "pem" or "engine", to determine from where the
|
||||
* private key for a TLS connection will be obtained. Defaults to
|
||||
* "pem", a normal private key file.
|
||||
* MOSQ_OPT_TLS_KPASS_SHA1
|
||||
* Where the TLS Engine requires the use of a password to be
|
||||
* accessed, this option allows a hex encoded SHA1 hash of the
|
||||
* private key password to be passed to the engine directly.
|
||||
* Must be set before <mosquitto_connect>.
|
||||
* MOSQ_OPT_TLS_ALPN
|
||||
* If the broker being connected to has multiple services available
|
||||
* on a single TLS port, such as both MQTT and WebSockets, use this
|
||||
* option to configure the ALPN option for the connection.
|
||||
*/
|
||||
libmosq_EXPORT int mosquitto_string_option(struct mosquitto *mosq, enum mosq_opt_t option, const char *value);
|
||||
|
||||
|
||||
/*
|
||||
* Function: mosquitto_reconnect_delay_set
|
||||
*
|
||||
|
@ -9,7 +9,7 @@
|
||||
!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
|
||||
|
||||
Name "Eclipse Mosquitto"
|
||||
!define VERSION 2.0.8
|
||||
!define VERSION 2.0.9
|
||||
OutFile "mosquitto-${VERSION}-install-windows-x86.exe"
|
||||
|
||||
InstallDir "$PROGRAMFILES\mosquitto"
|
||||
|
@ -9,7 +9,7 @@
|
||||
!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
|
||||
|
||||
Name "Eclipse Mosquitto"
|
||||
!define VERSION 2.0.8
|
||||
!define VERSION 2.0.9
|
||||
OutFile "mosquitto-${VERSION}-install-windows-x64.exe"
|
||||
|
||||
!include "x64.nsh"
|
||||
|
@ -203,6 +203,13 @@ static int interruptible_sleep(struct mosquitto *mosq, time_t reconnect_delay)
|
||||
char pairbuf;
|
||||
int maxfd = 0;
|
||||
|
||||
#ifndef WIN32
|
||||
if(read(mosq->sockpairR, &pairbuf, 1) == 0){
|
||||
}
|
||||
#else
|
||||
recv(mosq->sockpairR, &pairbuf, 1, 0);
|
||||
#endif
|
||||
|
||||
local_timeout.tv_sec = reconnect_delay;
|
||||
#ifdef HAVE_PSELECT
|
||||
local_timeout.tv_nsec = 0;
|
||||
|
@ -196,6 +196,7 @@ int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_st
|
||||
#ifdef WITH_TLS
|
||||
mosq->ssl = NULL;
|
||||
mosq->ssl_ctx = NULL;
|
||||
mosq->ssl_ctx_defaults = true;
|
||||
mosq->tls_cert_reqs = SSL_VERIFY_PEER;
|
||||
mosq->tls_insecure = false;
|
||||
mosq->want_write = false;
|
||||
|
@ -190,8 +190,8 @@ struct mosquitto_msg_data{
|
||||
#ifdef WITH_BROKER
|
||||
struct mosquitto_client_msg *inflight;
|
||||
struct mosquitto_client_msg *queued;
|
||||
unsigned long msg_bytes;
|
||||
unsigned long msg_bytes12;
|
||||
long msg_bytes;
|
||||
long msg_bytes12;
|
||||
int msg_count;
|
||||
int msg_count12;
|
||||
#else
|
||||
|
@ -632,7 +632,7 @@
|
||||
<option>31</option>, or the more verbose
|
||||
<option>mqttv5</option>, <option>mqttv311</option>, or
|
||||
<option>mqttv31</option>.
|
||||
Defaults to <option>311</option>.</para>
|
||||
Defaults to <option>5</option>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
MAJOR=2
|
||||
MINOR=0
|
||||
REVISION=8
|
||||
REVISION=9
|
||||
|
||||
sed -i "s/^VERSION=.*/VERSION=${MAJOR}.${MINOR}.${REVISION}/" config.mk
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: mosquitto
|
||||
version: 2.0.8
|
||||
version: 2.0.9
|
||||
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.
|
||||
|
@ -77,6 +77,11 @@ if (WITH_BUNDLED_DEPS)
|
||||
include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/deps)
|
||||
endif (WITH_BUNDLED_DEPS)
|
||||
|
||||
find_path(HAVE_SYS_EPOLL_H sys/epoll.h)
|
||||
if (HAVE_SYS_EPOLL_H)
|
||||
add_definitions("-DWITH_EPOLL")
|
||||
endif()
|
||||
|
||||
option(INC_BRIDGE_SUPPORT
|
||||
"Include bridge support for connecting to other brokers?" ON)
|
||||
if (INC_BRIDGE_SUPPORT)
|
||||
|
39
src/bridge.c
39
src/bridge.c
@ -112,6 +112,7 @@ int bridge__new(struct mosquitto__bridge *bridge)
|
||||
new_context->tls_alpn = new_context->bridge->tls_alpn;
|
||||
new_context->tls_engine = db.config->default_listener.tls_engine;
|
||||
new_context->tls_keyform = db.config->default_listener.tls_keyform;
|
||||
new_context->ssl_ctx_defaults = true;
|
||||
#ifdef FINAL_WITH_TLS_PSK
|
||||
new_context->tls_psk_identity = new_context->bridge->tls_psk_identity;
|
||||
new_context->tls_psk = new_context->bridge->tls_psk;
|
||||
@ -313,10 +314,8 @@ int bridge__connect_step3(struct mosquitto *context)
|
||||
|
||||
rc = send__connect(context, context->keepalive, context->clean_start, NULL);
|
||||
if(rc == MOSQ_ERR_SUCCESS){
|
||||
bridge__backoff_reset(context);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else if(rc == MOSQ_ERR_ERRNO && errno == ENOTCONN){
|
||||
bridge__backoff_reset(context);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
if(rc == MOSQ_ERR_TLS){
|
||||
@ -454,10 +453,8 @@ int bridge__connect(struct mosquitto *context)
|
||||
|
||||
rc2 = send__connect(context, context->keepalive, context->clean_start, NULL);
|
||||
if(rc2 == MOSQ_ERR_SUCCESS){
|
||||
bridge__backoff_reset(context);
|
||||
return rc;
|
||||
}else if(rc2 == MOSQ_ERR_ERRNO && errno == ENOTCONN){
|
||||
bridge__backoff_reset(context);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}else{
|
||||
if(rc2 == MOSQ_ERR_TLS){
|
||||
@ -562,6 +559,8 @@ int bridge__on_connect(struct mosquitto *context)
|
||||
}
|
||||
}
|
||||
|
||||
bridge__backoff_reset(context);
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
@ -646,11 +645,11 @@ void bridge__packet_cleanup(struct mosquitto *context)
|
||||
packet__cleanup(&(context->in_packet));
|
||||
}
|
||||
|
||||
static int rand_between(int base, int cap)
|
||||
static int rand_between(int low, int high)
|
||||
{
|
||||
int r;
|
||||
util__random_bytes(&r, sizeof(int));
|
||||
return (r % (cap - base)) + base;
|
||||
return (abs(r) % (high - low)) + low;
|
||||
}
|
||||
|
||||
static void bridge__backoff_step(struct mosquitto *context)
|
||||
@ -685,6 +684,33 @@ static void bridge__backoff_reset(struct mosquitto *context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void bridge_check_pending(struct mosquitto *context)
|
||||
{
|
||||
int err;
|
||||
socklen_t len;
|
||||
|
||||
if(context->state == mosq_cs_connect_pending){
|
||||
len = sizeof(int);
|
||||
if(!getsockopt(context->sock, SOL_SOCKET, SO_ERROR, (char *)&err, &len)){
|
||||
if(err == 0){
|
||||
mosquitto__set_state(context, mosq_cs_new);
|
||||
#if defined(WITH_ADNS) && defined(WITH_BRIDGE)
|
||||
if(context->bridge){
|
||||
bridge__connect_step3(context);
|
||||
}
|
||||
#endif
|
||||
}else if(err == ECONNREFUSED){
|
||||
do_disconnect(context, MOSQ_ERR_CONN_LOST);
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
do_disconnect(context, MOSQ_ERR_CONN_LOST);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bridge_check(void)
|
||||
{
|
||||
static time_t last_check = 0;
|
||||
@ -703,6 +729,7 @@ void bridge_check(void)
|
||||
|
||||
if(context->sock != INVALID_SOCKET){
|
||||
mosquitto__check_keepalive(context);
|
||||
bridge_check_pending(context);
|
||||
|
||||
/* Check for bridges that are not round robin and not currently
|
||||
* connected to their primary broker. */
|
||||
|
@ -53,7 +53,7 @@ bool db__ready_for_flight(struct mosquitto_msg_data *msgs, int qos)
|
||||
if(db.config->max_queued_messages == 0 && db.config->max_inflight_bytes == 0){
|
||||
return true;
|
||||
}
|
||||
valid_bytes = msgs->msg_bytes - db.config->max_inflight_bytes < db.config->max_queued_bytes;
|
||||
valid_bytes = ((msgs->msg_bytes - (ssize_t)db.config->max_inflight_bytes) < (ssize_t)db.config->max_queued_bytes);
|
||||
valid_count = msgs->msg_count - msgs->inflight_maximum < db.config->max_queued_messages;
|
||||
|
||||
if(db.config->max_queued_messages == 0){
|
||||
@ -90,8 +90,8 @@ bool db__ready_for_queue(struct mosquitto *context, int qos, struct mosquitto_ms
|
||||
{
|
||||
int source_count;
|
||||
int adjust_count;
|
||||
size_t source_bytes;
|
||||
size_t adjust_bytes = db.config->max_inflight_bytes;
|
||||
long source_bytes;
|
||||
ssize_t adjust_bytes = (ssize_t)db.config->max_inflight_bytes;
|
||||
bool valid_bytes;
|
||||
bool valid_count;
|
||||
|
||||
@ -893,6 +893,26 @@ int db__message_reconnect_reset(struct mosquitto *context)
|
||||
}
|
||||
|
||||
|
||||
int db__message_remove_incoming(struct mosquitto* context, uint16_t mid)
|
||||
{
|
||||
struct mosquitto_client_msg *tail, *tmp;
|
||||
|
||||
if(!context) return MOSQ_ERR_INVAL;
|
||||
|
||||
DL_FOREACH_SAFE(context->msgs_in.inflight, tail, tmp){
|
||||
if(tail->mid == mid) {
|
||||
if(tail->store->qos != 2){
|
||||
return MOSQ_ERR_PROTOCOL;
|
||||
}
|
||||
db__message_remove(&context->msgs_in, tail);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
int db__message_release_incoming(struct mosquitto *context, uint16_t mid)
|
||||
{
|
||||
struct mosquitto_client_msg *tail, *tmp;
|
||||
|
@ -244,7 +244,9 @@ int connect__on_authorised(struct mosquitto *context, void *auth_data_out, uint1
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if(context->keepalive > db.config->max_keepalive){
|
||||
if(db.config->max_keepalive &&
|
||||
(context->keepalive > db.config->max_keepalive || context->keepalive == 0)){
|
||||
|
||||
context->keepalive = db.config->max_keepalive;
|
||||
if(mosquitto_property_add_int16(&connack_props, MQTT_PROP_SERVER_KEEP_ALIVE, context->keepalive)){
|
||||
rc = MOSQ_ERR_NOMEM;
|
||||
|
@ -285,6 +285,18 @@ int handle__publish(struct mosquitto *context)
|
||||
if(msg->qos > 0){
|
||||
db__message_store_find(context, msg->source_mid, &stored);
|
||||
}
|
||||
|
||||
if(stored && msg->source_mid != 0 &&
|
||||
(stored->qos != msg->qos
|
||||
|| stored->payloadlen != msg->payloadlen
|
||||
|| strcmp(stored->topic, msg->topic)
|
||||
|| memcmp(stored->payload, msg->payload, msg->payloadlen) )){
|
||||
|
||||
log__printf(NULL, MOSQ_LOG_WARNING, "Reused message ID %u from %s detected. Clearing from storage.", msg->source_mid, context->id);
|
||||
db__message_remove_incoming(context, msg->source_mid);
|
||||
stored = NULL;
|
||||
}
|
||||
|
||||
if(!stored){
|
||||
if(msg->qos == 0
|
||||
|| db__ready_for_flight(&context->msgs_in, msg->qos)
|
||||
|
@ -642,6 +642,7 @@ int persist__restore(void);
|
||||
int db__message_count(int *count);
|
||||
int db__message_delete_outgoing(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_state expect_state, int qos);
|
||||
int db__message_insert(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir, uint8_t qos, bool retain, struct mosquitto_msg_store *stored, mosquitto_property *properties, bool update);
|
||||
int db__message_remove_incoming(struct mosquitto* context, uint16_t mid);
|
||||
int db__message_release_incoming(struct mosquitto *context, uint16_t mid);
|
||||
int db__message_update_outgoing(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_state state, int qos);
|
||||
void db__message_dequeue_first(struct mosquitto *context, struct mosquitto_msg_data *msg_data);
|
||||
|
67
src/net.c
67
src/net.c
@ -335,14 +335,14 @@ int net__tls_server_ctx(struct mosquitto__listener *listener)
|
||||
}else if(!strcmp(listener->tls_version, "tlsv1.3")){
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2);
|
||||
}else if(!strcmp(listener->tls_version, "tlsv1.2")){
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_3);
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
||||
}else if(!strcmp(listener->tls_version, "tlsv1.1")){
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3);
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
|
||||
#else
|
||||
}else if(!strcmp(listener->tls_version, "tlsv1.2")){
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
||||
}else if(!strcmp(listener->tls_version, "tlsv1.1")){
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_2);
|
||||
SSL_CTX_set_options(listener->ssl_ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
|
||||
#endif
|
||||
}else{
|
||||
log__printf(NULL, MOSQ_LOG_ERR, "Error: Unsupported tls_version \"%s\".", listener->tls_version);
|
||||
@ -624,23 +624,44 @@ static int net__bind_interface(struct mosquitto__listener *listener, struct addr
|
||||
&& ifa->ifa_addr->sa_family == rp->ai_addr->sa_family){
|
||||
|
||||
if(rp->ai_addr->sa_family == AF_INET){
|
||||
memcpy(&((struct sockaddr_in *)rp->ai_addr)->sin_addr,
|
||||
&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
|
||||
sizeof(struct in_addr));
|
||||
if(listener->host &&
|
||||
memcmp(&((struct sockaddr_in *)rp->ai_addr)->sin_addr,
|
||||
&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
|
||||
sizeof(struct in_addr))){
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface address for %s does not match specified listener address (%s).",
|
||||
listener->bind_interface, listener->host);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}else{
|
||||
memcpy(&((struct sockaddr_in *)rp->ai_addr)->sin_addr,
|
||||
&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
|
||||
sizeof(struct in_addr));
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}else if(rp->ai_addr->sa_family == AF_INET6){
|
||||
memcpy(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr,
|
||||
&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
freeifaddrs(ifaddr);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
if(listener->host &&
|
||||
memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr,
|
||||
&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
|
||||
sizeof(struct in6_addr))){
|
||||
|
||||
log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface address for %s does not match specified listener address (%s).",
|
||||
listener->bind_interface, listener->host);
|
||||
return MOSQ_ERR_INVAL;
|
||||
}else{
|
||||
memcpy(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr,
|
||||
&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
freeifaddrs(ifaddr);
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifaddr);
|
||||
log__printf(NULL, MOSQ_LOG_ERR, "Error: Interface %s not found.", listener->bind_interface);
|
||||
log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Interface %s does not support %s configuration.",
|
||||
listener->bind_interface, rp->ai_addr->sa_family == AF_INET ? "IPv4" : "IPv6");
|
||||
return MOSQ_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif
|
||||
@ -654,6 +675,9 @@ static int net__socket_listen_tcp(struct mosquitto__listener *listener)
|
||||
char service[10];
|
||||
int rc;
|
||||
int ss_opt = 1;
|
||||
#ifndef WIN32
|
||||
bool interface_bound = false;
|
||||
#endif
|
||||
|
||||
if(!listener) return MOSQ_ERR_INVAL;
|
||||
|
||||
@ -718,12 +742,14 @@ static int net__socket_listen_tcp(struct mosquitto__listener *listener)
|
||||
|
||||
#ifndef WIN32
|
||||
if(listener->bind_interface){
|
||||
/* It might be possible that an interface does not support all relevant sa_families.
|
||||
* We should successfully find at least one. */
|
||||
if(net__bind_interface(listener, rp)){
|
||||
COMPAT_CLOSE(sock);
|
||||
freeaddrinfo(ainfo);
|
||||
mosquitto__free(listener->socks);
|
||||
return 1;
|
||||
listener->sock_count--;
|
||||
continue;
|
||||
}
|
||||
interface_bound = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -745,6 +771,13 @@ static int net__socket_listen_tcp(struct mosquitto__listener *listener)
|
||||
}
|
||||
freeaddrinfo(ainfo);
|
||||
|
||||
#ifndef WIN32
|
||||
if(listener->bind_interface && !interface_bound){
|
||||
mosquitto__free(listener->socks);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,10 @@ int retain__store(const char *topic, struct mosquitto_msg_store *stored, char **
|
||||
assert(split_topics);
|
||||
|
||||
HASH_FIND(hh, db.retains, split_topics[0], strlen(split_topics[0]), retainhier);
|
||||
if(retainhier == NULL) return MOSQ_ERR_NOT_FOUND;
|
||||
if(retainhier == NULL){
|
||||
retainhier = retain__add_hier_entry(NULL, &db.retains, split_topics[0], (uint16_t)strlen(split_topics[0]));
|
||||
if(!retainhier) return MOSQ_ERR_NOMEM;
|
||||
}
|
||||
|
||||
for(i=0; split_topics[i] != NULL; i++){
|
||||
slen = strlen(split_topics[i]);
|
||||
|
61
test/broker/02-subpub-qos0-queued-bytes.py
Executable file
61
test/broker/02-subpub-qos0-queued-bytes.py
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from mosq_test_helper import *
|
||||
|
||||
def write_config(filename, port):
|
||||
with open(filename, 'w') as f:
|
||||
f.write("listener %d\n" % (port))
|
||||
f.write("allow_anonymous true\n")
|
||||
f.write("max_inflight_messages 20\n")
|
||||
f.write("max_inflight_bytes 1000000\n")
|
||||
f.write("max_queued_messages 20\n")
|
||||
f.write("max_queued_bytes 1000000\n")
|
||||
|
||||
def do_test(proto_ver):
|
||||
rc = 1
|
||||
keepalive = 60
|
||||
connect_packet = mosq_test.gen_connect("subpub-qos0-bytes", keepalive=keepalive, proto_ver=proto_ver)
|
||||
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
|
||||
|
||||
connect_packet_helper = mosq_test.gen_connect("qos0-bytes-helper", keepalive=keepalive, proto_ver=proto_ver)
|
||||
|
||||
mid = 1
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "subpub/qos0/queued/bytes", 1, proto_ver=proto_ver)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1, proto_ver=proto_ver)
|
||||
|
||||
publish_packet0 = mosq_test.gen_publish("subpub/qos0/queued/bytes", qos=0, payload="message", proto_ver=proto_ver)
|
||||
|
||||
|
||||
port = mosq_test.get_port()
|
||||
conf_file = os.path.basename(__file__).replace('.py', '.conf')
|
||||
write_config(conf_file, port)
|
||||
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=4, port=port, connack_error="connack 1")
|
||||
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
helper = mosq_test.do_client_connect(connect_packet_helper, connack_packet, timeout=4, port=port, connack_error="connack helper")
|
||||
|
||||
helper.send(publish_packet0)
|
||||
mosq_test.expect_packet(sock, "publish0", publish_packet0)
|
||||
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'))
|
||||
print("proto_ver=%d" % (proto_ver))
|
||||
exit(rc)
|
||||
|
||||
|
||||
do_test(proto_ver=4)
|
||||
do_test(proto_ver=5)
|
||||
exit(0)
|
@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet.
|
||||
|
||||
from mosq_test_helper import *
|
||||
|
||||
|
||||
def helper(port):
|
||||
connect_packet = mosq_test.gen_connect("test-helper", keepalive=60)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 128
|
||||
publish_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message")
|
||||
puback_packet = mosq_test.gen_puback(mid)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack")
|
||||
mosq_test.do_send_receive(sock, publish_packet, puback_packet, "helper puback")
|
||||
sock.close()
|
||||
|
||||
|
||||
def do_test(proto_ver):
|
||||
rc = 1
|
||||
mid = 3265
|
||||
keepalive = 60
|
||||
connect_packet = mosq_test.gen_connect("pub-qos1-timeout-test", keepalive=keepalive, proto_ver=proto_ver)
|
||||
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
|
||||
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/timeout/test", 1, proto_ver=proto_ver)
|
||||
suback_packet = mosq_test.gen_suback(mid, 1, proto_ver=proto_ver)
|
||||
|
||||
mid = 1
|
||||
publish_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message", proto_ver=proto_ver)
|
||||
publish_dup_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message", dup=True, proto_ver=proto_ver)
|
||||
puback_packet = mosq_test.gen_puback(mid, proto_ver=proto_ver)
|
||||
|
||||
port = mosq_test.get_port()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
helper(port)
|
||||
# Should have now received a publish command
|
||||
|
||||
mosq_test.expect_packet(sock, "publish", publish_packet)
|
||||
# Wait for longer than 5 seconds to get republish with dup set
|
||||
# This is covered by the 8 second timeout
|
||||
|
||||
mosq_test.expect_packet(sock, "dup publish", publish_dup_packet)
|
||||
sock.send(puback_packet)
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
print("proto_ver=%d" % (proto_ver))
|
||||
exit(rc)
|
||||
|
||||
|
||||
do_test(proto_ver=4)
|
||||
do_test(proto_ver=5)
|
||||
|
||||
exit(0)
|
||||
|
@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet.
|
||||
|
||||
from mosq_test_helper import *
|
||||
|
||||
|
||||
def helper(port):
|
||||
connect_packet = mosq_test.gen_connect("test-helper", keepalive=60)
|
||||
connack_packet = mosq_test.gen_connack(rc=0)
|
||||
|
||||
mid = 312
|
||||
publish_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message")
|
||||
pubrec_packet = mosq_test.gen_pubrec(mid)
|
||||
pubrel_packet = mosq_test.gen_pubrel(mid)
|
||||
pubcomp_packet = mosq_test.gen_pubcomp(mid)
|
||||
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack")
|
||||
mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "helper pubrec")
|
||||
mosq_test.do_send_receive(sock, pubrel_packet, pubcomp_packet, "helper pubcomp")
|
||||
sock.close()
|
||||
|
||||
|
||||
def do_test(proto_ver):
|
||||
rc = 1
|
||||
mid = 3265
|
||||
keepalive = 60
|
||||
connect_packet = mosq_test.gen_connect("pub-qo2-timeout-test", keepalive=keepalive, proto_ver=proto_ver)
|
||||
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
|
||||
|
||||
subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/timeout/test", 2, proto_ver=proto_ver)
|
||||
suback_packet = mosq_test.gen_suback(mid, 2, proto_ver=proto_ver)
|
||||
|
||||
mid = 1
|
||||
publish_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message", proto_ver=proto_ver)
|
||||
publish_dup_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message", dup=True, proto_ver=proto_ver)
|
||||
pubrec_packet = mosq_test.gen_pubrec(mid, proto_ver=proto_ver)
|
||||
pubrel_packet = mosq_test.gen_pubrel(mid, proto_ver=proto_ver)
|
||||
pubcomp_packet = mosq_test.gen_pubcomp(mid, proto_ver=proto_ver)
|
||||
|
||||
port = mosq_test.get_port()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet)
|
||||
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
|
||||
|
||||
helper(port)
|
||||
# Should have now received a publish command
|
||||
|
||||
mosq_test.expect_packet(sock, "publish", publish_packet)
|
||||
# Wait for longer than 5 seconds to get republish with dup set
|
||||
# This is covered by the 8 second timeout
|
||||
|
||||
mosq_test.expect_packet(sock, "dup publish", publish_dup_packet)
|
||||
mosq_test.do_send_receive(sock, pubrec_packet, pubrel_packet, "pubrel")
|
||||
|
||||
# Wait for longer than 5 seconds to get republish with dup set
|
||||
# This is covered by the 8 second timeout
|
||||
|
||||
mosq_test.expect_packet(sock, "dup pubrel", pubrel_packet)
|
||||
sock.send(pubcomp_packet)
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
print("proto_ver=%d" % (proto_ver))
|
||||
exit(rc)
|
||||
|
||||
|
||||
do_test(proto_ver=4)
|
||||
do_test(proto_ver=5)
|
||||
exit(0)
|
||||
|
@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet
|
||||
# flow. This test introduces delays into the flow in order to force the broker
|
||||
# to send duplicate PUBREC and PUBCOMP messages.
|
||||
|
||||
from mosq_test_helper import *
|
||||
|
||||
|
||||
def do_test(port):
|
||||
rc = 1
|
||||
keepalive = 600
|
||||
connect_packet = mosq_test.gen_connect("pub-qos2-timeout-test", keepalive=keepalive, proto_ver=proto_ver)
|
||||
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
|
||||
|
||||
mid = 1926
|
||||
publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="timeout-message", proto_ver=proto_ver)
|
||||
pubrec_packet = mosq_test.gen_pubrec(mid, proto_ver=proto_ver)
|
||||
pubrel_packet = mosq_test.gen_pubrel(mid, proto_ver=proto_ver)
|
||||
pubcomp_packet = mosq_test.gen_pubcomp(mid, proto_ver=proto_ver)
|
||||
|
||||
port = mosq_test.get_port()
|
||||
broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port)
|
||||
|
||||
try:
|
||||
sock = mosq_test.do_client_connect(connect_packet, connack_packet)
|
||||
mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "pubrec")
|
||||
|
||||
# Timeout is 8 seconds which means the broker should repeat the PUBREC.
|
||||
|
||||
mosq_test.expect_packet(sock, "pubrec", pubrec_packet)
|
||||
mosq_test.do_send_receive(sock, pubrel_packet, pubcomp_packet, "pubcomp")
|
||||
|
||||
rc = 0
|
||||
|
||||
sock.close()
|
||||
except mosq_test.TestError:
|
||||
pass
|
||||
finally:
|
||||
broker.terminate()
|
||||
broker.wait()
|
||||
(stdo, stde) = broker.communicate()
|
||||
if rc:
|
||||
print(stde.decode('utf-8'))
|
||||
print("proto_ver=%d" % (proto_ver))
|
||||
exit(rc)
|
||||
|
||||
|
||||
do_test(proto_ver=4)
|
||||
do_test(proto_ver=5)
|
||||
exit(0)
|
||||
|
@ -45,6 +45,7 @@ test : test-compile 01 02 03 04 05 06 07 08 09 10 11 12 13 14
|
||||
./02-subhier-crash.py
|
||||
./02-subpub-qos0-long-topic.py
|
||||
./02-subpub-qos0-oversize-payload.py
|
||||
./02-subpub-qos0-queued-bytes.py
|
||||
./02-subpub-qos0-retain-as-publish.py
|
||||
./02-subpub-qos0-send-retain.py
|
||||
./02-subpub-qos0-subscription-id.py
|
||||
|
@ -28,6 +28,7 @@ tests = [
|
||||
(1, './02-subhier-crash.py'),
|
||||
(1, './02-subpub-qos0-long-topic.py'),
|
||||
(1, './02-subpub-qos0-oversize-payload.py'),
|
||||
(1, './02-subpub-qos0-queued-bytes.py'),
|
||||
(1, './02-subpub-qos0-retain-as-publish.py'),
|
||||
(1, './02-subpub-qos0-send-retain.py'),
|
||||
(1, './02-subpub-qos0-subscription-id.py'),
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
.. title: Download
|
||||
.. slug: download
|
||||
.. date: 2022-02-25 17:18:38 UTC
|
||||
.. date: 2022-03-11 22:16:38 UTC
|
||||
.. tags: tag
|
||||
.. category: category
|
||||
.. link: link
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
# Source
|
||||
|
||||
* [mosquitto-2.0.8.tar.gz](https://mosquitto.org/files/source/mosquitto-2.0.8.tar.gz) (319kB) ([GPG signature](https://mosquitto.org/files/source/mosquitto-2.0.8.tar.gz.asc))
|
||||
* [mosquitto-2.0.9.tar.gz](https://mosquitto.org/files/source/mosquitto-2.0.9.tar.gz) (319kB) ([GPG signature](https://mosquitto.org/files/source/mosquitto-2.0.9.tar.gz.asc))
|
||||
* [Git source code repository](https://github.com/eclipse/mosquitto) (github.com)
|
||||
|
||||
Older downloads are available at [https://mosquitto.org/files/](../files/)
|
||||
@ -24,8 +24,8 @@ distributions.
|
||||
|
||||
## Windows
|
||||
|
||||
* [mosquitto-2.0.8-install-windows-x64.exe](https://mosquitto.org/files/binary/win64/mosquitto-2.0.8-install-windows-x64.exe) (64-bit build, Windows Vista and up, built with Visual Studio Community 2019)
|
||||
* [mosquitto-2.0.8-install-windows-x32.exe](https://mosquitto.org/files/binary/win32/mosquitto-2.0.8-install-windows-x86.exe) (32-bit build, Windows Vista and up, built with Visual Studio Community 2019)
|
||||
* [mosquitto-2.0.9-install-windows-x64.exe](https://mosquitto.org/files/binary/win64/mosquitto-2.0.9-install-windows-x64.exe) (64-bit build, Windows Vista and up, built with Visual Studio Community 2019)
|
||||
* [mosquitto-2.0.9-install-windows-x32.exe](https://mosquitto.org/files/binary/win32/mosquitto-2.0.9-install-windows-x86.exe) (32-bit build, Windows Vista and up, built with Visual Studio Community 2019)
|
||||
|
||||
Older installers can be found at [https://mosquitto.org/files/binary/](https://mosquitto.org/files/binary/).
|
||||
|
||||
|
101
www/posts/2021/03/version-2-0-9-released.md
Normal file
101
www/posts/2021/03/version-2-0-9-released.md
Normal file
@ -0,0 +1,101 @@
|
||||
<!--
|
||||
.. title: Version 2.0.9 released.
|
||||
.. slug: version-2-0-9-released
|
||||
.. date: 2021-03-11 22:19:38 UTC
|
||||
.. tags: Releases
|
||||
.. category:
|
||||
.. link:
|
||||
.. description:
|
||||
.. type: text
|
||||
-->
|
||||
|
||||
Versions 2.0.9, 1.6.14, and 1.5.11 of Mosquitto have been released. These are
|
||||
bugfix releases and include a minor security fix.
|
||||
|
||||
# 2.0.9
|
||||
|
||||
## Security
|
||||
- If an empty or invalid CA file was provided to the client library for
|
||||
verifying the remote broker, then the initial connection would fail but
|
||||
subsequent connections would succeed without verifying the remote broker
|
||||
certificate. Closes [#2130].
|
||||
- If an empty or invalid CA file was provided to the broker for verifying the
|
||||
remote broker for an outgoing bridge connection then the initial connection
|
||||
would fail but subsequent connections would succeed without verifying the
|
||||
remote broker certificate. Closes [#2130].
|
||||
|
||||
## Broker
|
||||
- Fix encrypted bridge connections incorrectly connecting when `bridge_cafile`
|
||||
is empty or invalid. Closes [#2130].
|
||||
- Fix `tls_version` behaviour not matching documentation. It was setting the
|
||||
exact TLS version to use, not the minimium TLS version to use. Closes [#2110].
|
||||
- Fix messages to `$` prefixed topics being rejected. Closes [#2111].
|
||||
- Fix QoS 0 messages not being delivered when max_queued_bytes was configured.
|
||||
Closes [#2123].
|
||||
- Fix bridge increasing backoff calculation.
|
||||
- Improve handling of invalid combinations of listener address and bind
|
||||
interface configurations. Closes [#2081].
|
||||
- Fix `max_keepalive` option not applying to clients connecting with keepalive
|
||||
set to 0. Closes [#2117].
|
||||
|
||||
## Client library
|
||||
- Fix encrypted connections incorrectly connecting when the CA file passed to
|
||||
`mosquitto_tls_set()` is empty or invalid. Closes [#2130].
|
||||
- Fix connections retrying very rapidly in some situations.
|
||||
|
||||
## Build
|
||||
- Fix cmake epoll detection.
|
||||
|
||||
# 1.6.14
|
||||
|
||||
## Security
|
||||
- If an empty or invalid CA file was provided to the client library for
|
||||
verifying the remote broker, then the initial connection would fail but
|
||||
subsequent connections would succeed without verifying the remote broker
|
||||
certificate. Closes [#2130].
|
||||
- If an empty or invalid CA file was provided to the broker for verifying the
|
||||
remote broker for an outgoing bridge connection then the initial connection
|
||||
would fail but subsequent connections would succeed without verifying the
|
||||
remote broker certificate. Closes [#2130].
|
||||
|
||||
## Broker
|
||||
- Fix encrypted bridge connections incorrectly connecting when `bridge_cafile`
|
||||
is empty or invalid. Closes [#2130].
|
||||
|
||||
## Client library
|
||||
- Fix encrypted connections incorrectly connecting when the CA file passed to
|
||||
`mosquitto_tls_set()` is empty or invalid. Closes [#2130].
|
||||
- Fix connections retrying very rapidly in some situations.
|
||||
|
||||
## Clients
|
||||
- Fix possible loss of data in `mosquitto_pub -l` when sending multiple long
|
||||
lines. Closes [#2078].
|
||||
|
||||
# 1.5.11
|
||||
|
||||
## Security
|
||||
- If an empty or invalid CA file was provided to the client library for
|
||||
verifying the remote broker, then the initial connection would fail but
|
||||
subsequent connections would succeed without verifying the remote broker
|
||||
certificate. Closes [#2130].
|
||||
- If an empty or invalid CA file was provided to the broker for verifying the
|
||||
remote broker for an outgoing bridge connection then the initial connection
|
||||
would fail but subsequent connections would succeed without verifying the
|
||||
remote broker certificate. Closes [#2130].
|
||||
|
||||
## Broker
|
||||
- Fix encrypted bridge connections incorrectly connecting when `bridge_cafile`
|
||||
is empty or invalid. Closes [#2130].
|
||||
|
||||
## Client library
|
||||
- Fix encrypted connections incorrectly connecting when the CA file passed to
|
||||
`mosquitto_tls_set()` is empty or invalid. Closes [#2130].
|
||||
|
||||
[#2040]: https://github.com/eclipse/mosquitto/issues/2040
|
||||
[#2078]: https://github.com/eclipse/mosquitto/issues/2078
|
||||
[#2081]: https://github.com/eclipse/mosquitto/issues/2081
|
||||
[#2110]: https://github.com/eclipse/mosquitto/issues/2110
|
||||
[#2111]: https://github.com/eclipse/mosquitto/issues/2111
|
||||
[#2117]: https://github.com/eclipse/mosquitto/issues/2117
|
||||
[#2123]: https://github.com/eclipse/mosquitto/issues/2123
|
||||
[#2130]: https://github.com/eclipse/mosquitto/issues/2130
|
Loading…
Reference in New Issue
Block a user