diff --git a/ChangeLog.txt b/ChangeLog.txt index 4021e1bc..de5cedd3 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -18,6 +18,8 @@ Broker: plugins to determine which version of MQTT a client has connected with. - Send DISCONNECT with `malformed-packet` reason code on invalid PUBLISH, SUBSCRIBE, and UNSUBSCRIBE packets. +- Add `mosquitto_kick_client_by_clientid()` and `mosquitto_kick_client_by_username()` + functions, which can be used by plugins to disconnect clients. Client library: - Client no longer generates random client ids for v3.1.1 clients, these are diff --git a/lib/mosquitto.h b/lib/mosquitto.h index 9f9f996f..6b2d27ae 100644 --- a/lib/mosquitto.h +++ b/lib/mosquitto.h @@ -101,6 +101,7 @@ enum mosq_err_t { MOSQ_ERR_TIMEOUT = 27, MOSQ_ERR_RETAIN_NOT_SUPPORTED = 28, MOSQ_ERR_TOPIC_ALIAS_INVALID = 29, + MOSQ_ERR_ADMINISTRATIVE_ACTION = 30, }; /* Option values */ diff --git a/src/linker-macosx.syms b/src/linker-macosx.syms index f05a8721..269cc699 100644 --- a/src/linker-macosx.syms +++ b/src/linker-macosx.syms @@ -9,6 +9,8 @@ _mosquitto_client_protocol _mosquitto_client_protocol_version _mosquitto_client_sub_count _mosquitto_client_username +_mosquitto_kick_client_by_clientid +_mosquitto_kick_client_by_username _mosquitto_log_printf _mosquitto_property_add_binary _mosquitto_property_add_byte diff --git a/src/linker.syms b/src/linker.syms index 6fd45b43..9e7e6647 100644 --- a/src/linker.syms +++ b/src/linker.syms @@ -10,6 +10,8 @@ mosquitto_client_protocol_version; mosquitto_client_sub_count; mosquitto_client_username; + mosquitto_kick_client_by_clientid; + mosquitto_kick_client_by_username; mosquitto_log_printf; mosquitto_plugin_publish; mosquitto_property_add_binary; diff --git a/src/loop.c b/src/loop.c index 60eda9d2..8e876025 100644 --- a/src/loop.c +++ b/src/loop.c @@ -300,12 +300,19 @@ void do_disconnect(struct mosquitto_db *db, struct mosquitto *context, int reaso case MOSQ_ERR_KEEPALIVE: log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s has exceeded timeout, disconnecting.", id); break; + case MOSQ_ERR_ADMINISTRATIVE_ACTION: + log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id); + break; default: log__printf(NULL, MOSQ_LOG_NOTICE, "Socket error on client %s, disconnecting.", id); break; } }else{ - log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected.", id); + if(reason == MOSQ_ERR_ADMINISTRATIVE_ACTION){ + log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id); + }else{ + log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected.", id); + } } } mux__delete(db, context); diff --git a/src/mosquitto_broker.h b/src/mosquitto_broker.h index b9236563..36e40b63 100644 --- a/src/mosquitto_broker.h +++ b/src/mosquitto_broker.h @@ -179,6 +179,46 @@ const char *mosquitto_client_username(const struct mosquitto *client); int mosquitto_set_username(struct mosquitto *client, const char *username); +/* ========================================================================= + * + * Client control + * + * ========================================================================= */ + +/* Function: mosquitto_kick_client_by_clientid + * + * Forcefully disconnect a client from the broker. + * + * If clientid != NULL, then the client with the matching client id is + * disconnected from the broker. + * If clientid == NULL, then all clients are disconnected from the broker. + * + * If with_will == true, then if the client has a Last Will and Testament + * defined then this will be sent. If false, the LWT will not be sent. + */ +int mosquitto_kick_client_by_clientid(const char *clientid, bool with_will); + +/* Function: mosquitto_kick_client_by_username + * + * Forcefully disconnect a client from the broker. + * + * If username != NULL, then all clients with a matching username are kicked + * from the broker. + * If username == NULL, then all clients that do not have a username are + * kicked. + * + * If with_will == true, then if the client has a Last Will and Testament + * defined then this will be sent. If false, the LWT will not be sent. + */ +int mosquitto_kick_client_by_username(const char *username, bool with_will); + + +/* ========================================================================= + * + * Publishing functions + * + * ========================================================================= */ + /* Function: mosquitto_broker_publish * * Publish a message from within a plugin. diff --git a/src/plugin.c b/src/plugin.c index 6487d773..f7cebc91 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -20,6 +20,9 @@ Contributors: #include "mosquitto_internal.h" #include "mosquitto_broker.h" #include "memory_mosq.h" +#include "mqtt_protocol.h" +#include "send_mosq.h" +#include "util_mosq.h" #include "utlist.h" #ifdef WITH_TLS @@ -230,3 +233,59 @@ int mosquitto_set_username(struct mosquitto *client, const char *username) } } + +static void disconnect_client(struct mosquitto_db *db, struct mosquitto *context, bool with_will) +{ + if(context->protocol == mosq_p_mqtt5){ + send__disconnect(context, MQTT_RC_ADMINISTRATIVE_ACTION, NULL); + } + if(with_will == false){ + mosquitto__set_state(context, mosq_cs_disconnecting); + } + do_disconnect(db, context, MOSQ_ERR_ADMINISTRATIVE_ACTION); +} + +int mosquitto_kick_client_by_clientid(const char *clientid, bool with_will) +{ + struct mosquitto_db *db; + struct mosquitto *ctxt, *ctxt_tmp; + + db = mosquitto__get_db(); + if(clientid == NULL){ + HASH_ITER(hh_sock, db->contexts_by_sock, ctxt, ctxt_tmp){ + disconnect_client(db, ctxt, with_will); + } + return MOSQ_ERR_SUCCESS; + }else{ + HASH_FIND(hh_id, db->contexts_by_id, clientid, strlen(clientid), ctxt); + if(ctxt){ + disconnect_client(db, ctxt, with_will); + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_NOT_FOUND; + } + } +} + +int mosquitto_kick_client_by_username(const char *username, bool with_will) +{ + struct mosquitto_db *db; + struct mosquitto *ctxt, *ctxt_tmp; + + db = mosquitto__get_db(); + + if(username == NULL){ + HASH_ITER(hh_sock, db->contexts_by_sock, ctxt, ctxt_tmp){ + if(ctxt->username == NULL){ + disconnect_client(db, ctxt, with_will); + } + } + }else{ + HASH_ITER(hh_sock, db->contexts_by_sock, ctxt, ctxt_tmp){ + if(ctxt->username != NULL && !strcmp(ctxt->username, username)){ + disconnect_client(db, ctxt, with_will); + } + } + } + return MOSQ_ERR_SUCCESS; +}