Plugin delayed authentication.

This commit is contained in:
Roger A. Light
2021-05-19 16:54:26 +01:00
parent 9c9ca33d63
commit 0d3870585b
24 changed files with 481 additions and 11 deletions
+1
View File
@@ -18,6 +18,7 @@ Broker:
- Report on what compile time options are enabled. Closes #2193.
- Add `--tls-keylog` option which can be used to generate a file that can be
used by wireshark to decrypt TLS traffic for debugging purposes. Closes #1818.
- Add support for delayed basic authentication in plugins.
Client library:
- Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair
+1
View File
@@ -85,6 +85,7 @@ extern "C" {
/* Error values */
enum mosq_err_t {
MOSQ_ERR_AUTH_DELAYED = -5,
MOSQ_ERR_AUTH_CONTINUE = -4,
MOSQ_ERR_NO_SUBSCRIBERS = -3,
MOSQ_ERR_SUB_EXISTS = -2,
+25
View File
@@ -592,4 +592,29 @@ mosq_EXPORT int mosquitto_broker_publish_copy(
}
#endif
/* Function: mosquitto_complete_basic_auth
*
* Complete a delayed authentication request.
*
* Useful for plugins that subscribe to the MOSQ_EVT_BASIC_AUTH event. If your
* plugin makes authentication requests that are not "instant", in particular
* if they communicate with an external service, then instead of blocking for a
* reply and returning MOSQ_ERR_SUCCESS or MOSQ_ERR_AUTH, the plugin can return
* MOSQ_ERR_AUTH_DELAYED. This means that the plugin is promising to tell the
* broker the authentication result in the future. Once the plugin has an
* answer, it should call `mosquitto_complete_basic_auth()` passing the client
* id and the result.
*
* Result:
* MOSQ_ERR_SUCCESS - the client successfully authenticated
* MOSQ_ERR_AUTH - the client authentication failed
*
* Other error codes can be used if more appropriate, and the client connection
* will still be rejected, e.g. MOSQ_ERR_NOMEM.
*
* The plugin may use extra threads to handle the authentication requests, but
* the call to `mosquitto_complete_basic_auth()` must happen in the main
* mosquitto thread. Using the MOSQ_EVT_TICK event for this is suggested.
*/
void mosquitto_complete_basic_auth(const char *client_id, int result);
#endif
+1
View File
@@ -114,6 +114,7 @@ enum mosquitto_client_state {
mosq_cs_disused = 19, /* client that has been added to the disused list to be freed */
mosq_cs_authenticating = 20, /* Client has sent CONNECT but is still undergoing extended authentication */
mosq_cs_reauthenticating = 21, /* Client is undergoing reauthentication and shouldn't do anything else until complete */
mosq_cs_delayed_auth = 22, /* Client is awaiting an authentication result from a plugin */
};
enum mosquitto__protocol {
+8
View File
@@ -26,6 +26,14 @@ disconnect events. It publishes messages to
$SYS/broker/connection/client/<client id>/state for every client that connects
to the broker, to indicate the connection state of that client.
## Examples / Deferred authentication
This is an **example** plugin to demonstrate how a plugin can carry out
delayed basic authentication. This method should be used where the plugin
sends an authentication request to an external server so that if there is a
delay in getting a response it does not block the broker. The plugin may spawn
extra threads to handle the authentication requests, but the call to
`mosquitto_complete_basic_auth()` must happen in the main Mosquitto thread.
## Examples / Message timestamp
This is an **example** plugin to demonstrate how it is possible to attach MQTT
v5 properties to messages after they have been received, and before they are
+1
View File
@@ -5,4 +5,5 @@ endif()
add_subdirectory(auth-by-ip)
add_subdirectory(client-properties)
add_subdirectory(connection-state)
add_subdirectory(delayed-auth)
add_subdirectory(payload-modification)
+1
View File
@@ -3,6 +3,7 @@ DIRS= \
auth-by-ip \
client-properties \
connection-state \
delayed-auth \
message-timestamp \
payload-modification
@@ -0,0 +1,18 @@
add_library(mosquitto_delayed_auth SHARED
mosquitto_delayed_auth.c
)
target_include_directories(mosquitto_delayed_auth PRIVATE
"${STDBOOL_H_PATH}"
"${STDINT_H_PATH}"
"${mosquitto_SOURCE_DIR}"
"${mosquitto_SOURCE_DIR}/include"
)
set_target_properties(mosquitto_delayed_auth PROPERTIES
PREFIX ""
POSITION_INDEPENDENT_CODE 1
)
# Don't install, these are example plugins only.
#install(TARGETS mosquitto_delayed_auth RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+28
View File
@@ -0,0 +1,28 @@
include ../../../config.mk
.PHONY : all binary check clean reallyclean test install uninstall
PLUGIN_NAME=mosquitto_delayed_auth
PLUGIN_CFLAGS+=-I../../../include -I../../../
all : binary
binary : ${PLUGIN_NAME}.so
${PLUGIN_NAME}.so : ${PLUGIN_NAME}.c
$(CROSS_COMPILE)$(CC) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) $(PLUGIN_LDFLAGS) -fPIC -shared $< -o $@
reallyclean : clean
clean:
-rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno
check: test
test:
install: ${PLUGIN_NAME}.so
# Don't install, these are examples only.
#$(INSTALL) -d "${DESTDIR}$(libdir)"
#$(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${libdir}/${PLUGIN_NAME}.so"
uninstall :
-rm -f "${DESTDIR}${libdir}/${PLUGIN_NAME}.so"
@@ -0,0 +1,176 @@
/*
Copyright (c) 2021 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License 2.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
https://www.eclipse.org/legal/epl-2.0/
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
SPDX-License-Identifier: EPL-2.0 OR EDL-1.0
Contributors:
Roger Light - initial implementation and documentation.
*/
/*
* This is an example plugin showing how to carry out delayed authentication.
* The "authentication" in this example makes no checks whatsoever, but delays
* the response by 5 seconds, and randomly chooses whether it should succeed.
*
* Compile with:
* gcc -I<path to mosquitto-repo/include> -fPIC -shared mosquitto_delayed_auth.c -o mosquitto_delayed_auth.so
*
* Use in config with:
*
* plugin /path/to/mosquitto_delayed_auth.so
*
* Note that this only works on Mosquitto 2.0 or later.
*/
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <uthash.h>
#include "mosquitto_broker.h"
#include "mosquitto_plugin.h"
#include "mosquitto.h"
#include "mqtt_protocol.h"
#ifndef UNUSED
# define UNUSED(A) (void)(A)
#endif
struct client_list{
UT_hash_handle hh;
char *id;
time_t request_time;
};
static mosquitto_plugin_id_t *mosq_pid = NULL;
static struct client_list *clients = NULL;
static time_t last_check = 0;
bool authentication_check(struct client_list *client, time_t now)
{
time_t secs;
secs = now - client->request_time;
return secs > 5 ? true : false;
}
static int basic_auth_callback(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_basic_auth *ed = event_data;
static struct client_list *client;
const char *id;
UNUSED(event);
UNUSED(userdata);
id = mosquitto_client_id(ed->client);
HASH_FIND(hh, clients, id, strlen(id), client);
if(client){
client->request_time = time(NULL);
}else{
client = mosquitto_malloc(sizeof(struct client_list));
if(client == NULL){
return MOSQ_ERR_NOMEM;
}
client->id = mosquitto_strdup(id);
if(client->id == NULL){
mosquitto_free(client);
return MOSQ_ERR_NOMEM;
}
client->request_time = time(NULL);
HASH_ADD_KEYPTR(hh, clients, client->id, strlen(client->id), client);
mosquitto_log_printf(MOSQ_LOG_DEBUG, "Starting auth for %s at %d", client->id, time(NULL));
}
return MOSQ_ERR_AUTH_DELAYED;
}
static int tick_callback(int event, void *event_data, void *userdata)
{
struct client_list *client, *client_tmp;
time_t now;
long r;
UNUSED(event);
UNUSED(event_data);
UNUSED(userdata);
now = time(NULL);
if(now > last_check){
HASH_ITER(hh, clients, client, client_tmp){
if(authentication_check(client, now)){
/* Deny access 1/4 of the time, yes it's biased number generation. */
r = random() % 1000;
if(r > 740){
mosquitto_complete_basic_auth(client->id, MOSQ_ERR_AUTH);
}else{
mosquitto_complete_basic_auth(client->id, MOSQ_ERR_SUCCESS);
}
mosquitto_log_printf(MOSQ_LOG_DEBUG, "Completing auth for %s at %d", client->id, now);
HASH_DELETE(hh, clients, client);
mosquitto_free(client->id);
mosquitto_free(client);
}
}
last_check = now;
}
return MOSQ_ERR_SUCCESS;
}
int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
{
int i;
for(i=0; i<supported_version_count; i++){
if(supported_versions[i] == 5){
return 5;
}
}
return -1;
}
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count)
{
int rc;
UNUSED(user_data);
UNUSED(opts);
UNUSED(opt_count);
mosq_pid = identifier;
rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_BASIC_AUTH, basic_auth_callback, NULL, NULL);
if(rc) return rc;
rc = mosquitto_callback_register(mosq_pid, MOSQ_EVT_TICK, tick_callback, NULL, NULL);
return rc;
}
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int opt_count)
{
UNUSED(user_data);
UNUSED(opts);
UNUSED(opt_count);
mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_BASIC_AUTH, basic_auth_callback, NULL);
mosquitto_callback_unregister(mosq_pid, MOSQ_EVT_TICK, tick_callback, NULL);
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
listener 1883
plugin ./mosquitto_delayed_auth.so
sys_interval 1
log_timestamp_format %Y-%m-%dT%H:%M:%S
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
../../src/mosquitto -c test.conf -v
+7 -2
View File
@@ -69,8 +69,8 @@ struct mosquitto *context__init(void)
mosquitto__set_state(context, mosq_cs_new);
context->sock = INVALID_SOCKET;
context->last_msg_in = db.now_s;
context->next_msg_out = db.now_s + 60;
context->keepalive = 60; /* Default to 60s */
context->next_msg_out = db.now_s + 20;
context->keepalive = 20; /* Default to 20s */
context->clean_start = true;
context->id = NULL;
context->last_mid = 0;
@@ -294,6 +294,11 @@ void context__remove_from_by_id(struct mosquitto *context)
struct mosquitto *context_found;
if(context->removed_from_by_id == false && context->id){
HASH_FIND(hh_id, db.contexts_by_id_delayed_auth, context->id, strlen(context->id), context_found);
if(context_found){
HASH_DELETE(hh_id, db.contexts_by_id_delayed_auth, context_found);
}
HASH_FIND(hh_id, db.contexts_by_id, context->id, strlen(context->id), context_found);
if(context_found){
HASH_DELETE(hh_id, db.contexts_by_id, context_found);
+14 -1
View File
@@ -406,6 +406,7 @@ int handle__connect(struct mosquitto *context)
uint8_t protocol_version;
uint8_t connect_flags;
char *client_id = NULL;
struct mosquitto *found_context;
struct mosquitto_message_all *will_struct = NULL;
uint8_t will, will_retain, will_qos, clean_start;
uint8_t username_flag, password_flag;
@@ -629,6 +630,13 @@ int handle__connect(struct mosquitto *context)
}
}
/* Check for an existing delayed auth check, reject if present */
HASH_FIND(hh_id, db.contexts_by_id_delayed_auth, client_id, strlen(client_id), found_context);
if(found_context){
rc = MOSQ_ERR_UNKNOWN;
goto handle_connect_error;
}
if(will){
rc = will__read(context, client_id, &will_struct, will_qos, will_retain);
if(rc) goto handle_connect_error;
@@ -889,7 +897,7 @@ int handle__connect(struct mosquitto *context)
#endif
{
rc = mosquitto_unpwd_check(context);
if(rc != MOSQ_ERR_SUCCESS){
if(rc != MOSQ_ERR_SUCCESS && rc != MOSQ_ERR_AUTH_DELAYED){
/* We must have context->id == NULL here so we don't later try and
* remove the client from the by_id hash table */
mosquitto__free(context->id);
@@ -898,6 +906,11 @@ int handle__connect(struct mosquitto *context)
switch(rc){
case MOSQ_ERR_SUCCESS:
break;
case MOSQ_ERR_AUTH_DELAYED:
mosquitto__set_state(context, mosq_cs_delayed_auth);
HASH_ADD_KEYPTR(hh_id, db.contexts_by_id_delayed_auth, context->id, strlen(context->id), context);
return MOSQ_ERR_SUCCESS;
break;
case MOSQ_ERR_AUTH:
if(context->protocol == mosq_p_mqtt5){
send__connack(context, 0, MQTT_RC_NOT_AUTHORIZED, NULL);
+1
View File
@@ -12,6 +12,7 @@ _mosquitto_client_protocol
_mosquitto_client_protocol_version
_mosquitto_client_sub_count
_mosquitto_client_username
_mosquitto_complete_basic_auth
_mosquitto_free
_mosquitto_kick_client_by_clientid
_mosquitto_kick_client_by_username
+1
View File
@@ -13,6 +13,7 @@
mosquitto_client_protocol_version;
mosquitto_client_sub_count;
mosquitto_client_username;
mosquitto_complete_basic_auth;
mosquitto_free;
mosquitto_kick_client_by_clientid;
mosquitto_kick_client_by_username;
+1
View File
@@ -445,6 +445,7 @@ struct mosquitto_db{
struct mosquitto__retainhier *retains;
struct mosquitto *contexts_by_id;
struct mosquitto *contexts_by_sock;
struct mosquitto *contexts_by_id_delayed_auth;
struct mosquitto *contexts_for_free;
#ifdef WITH_BRIDGE
struct mosquitto **bridges;
+26
View File
@@ -27,6 +27,7 @@ Contributors:
#include "util_mosq.h"
#include "utlist.h"
#include "lib_load.h"
#include "will_mosq.h"
static bool check_callback_exists(struct mosquitto__callback *cb_base, MOSQ_FUNC_generic_callback cb_func)
@@ -345,3 +346,28 @@ int mosquitto_callback_unregister(
return remove_callback(cb_base, cb_func);
}
void mosquitto_complete_basic_auth(const char *client_id, int result)
{
struct mosquitto *context;
if(client_id == NULL) return;
HASH_FIND(hh_id, db.contexts_by_id_delayed_auth, client_id, strlen(client_id), context);
if(context){
HASH_DELETE(hh_id, db.contexts_by_id_delayed_auth, context);
if(result == MOSQ_ERR_SUCCESS){
connect__on_authorised(context, NULL, 0);
}else{
if(context->protocol == mosq_p_mqtt5){
send__connack(context, 0, MQTT_RC_NOT_AUTHORIZED, NULL);
}else{
send__connack(context, 0, CONNACK_REFUSED_NOT_AUTHORIZED, NULL);
}
context->clean_start = true;
context->session_expiry_interval = 0;
will__clear(context);
do_disconnect(context, MOSQ_ERR_AUTH);
}
}
}
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
# Test whether message parameters are passed to the plugin acl check function.
from mosq_test_helper import *
def write_config(filename, port):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port))
f.write("auth_plugin c/auth_plugin_delayed.so\n")
f.write("allow_anonymous false\n")
def do_test(proto_ver):
port = mosq_test.get_port()
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port)
rc = 1
connect_packet = mosq_test.gen_connect("delayed-auth-test", keepalive=42, username="delayed-username", password="good", proto_ver=proto_ver)
connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
connect_packet2 = mosq_test.gen_connect("delayed-auth-test", keepalive=42, username="delayed-username", password="bad", proto_ver=proto_ver)
if proto_ver == 5:
connack_packet2 = mosq_test.gen_connack(rc=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=proto_ver, property_helper=False)
else:
connack_packet2 = mosq_test.gen_connack(rc=5, 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=20, port=port)
sock.close()
sock = mosq_test.do_client_connect(connect_packet2, connack_packet2, timeout=20, port=port)
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(4)
do_test(5)
+1
View File
@@ -194,6 +194,7 @@ endif
./09-plugin-auth-unpwd-success.py
./09-plugin-auth-v2-unpwd-fail.py
./09-plugin-auth-v2-unpwd-success.py
./09-plugin-delayed-auth.py
./09-plugin-publish.py
./09-pwfile-parse-invalid.py
+7 -6
View File
@@ -3,19 +3,20 @@
CFLAGS=-I../../../include -Wall -Werror
PLUGIN_SRC = \
auth_plugin_v4.c \
auth_plugin_v5.c \
auth_plugin_v5_handle_message.c \
auth_plugin_pwd.c \
auth_plugin_acl.c \
auth_plugin_acl_sub_denied.c \
auth_plugin_v2.c \
auth_plugin_context_params.c \
auth_plugin_msg_params.c \
auth_plugin_delayed.c \
auth_plugin_extended_multiple.c \
auth_plugin_extended_single.c \
auth_plugin_extended_single2.c \
auth_plugin_msg_params.c \
auth_plugin_publish.c \
auth_plugin_pwd.c \
auth_plugin_v2.c \
auth_plugin_v4.c \
auth_plugin_v5.c \
auth_plugin_v5_handle_message.c \
plugin_control.c
PLUGINS = ${PLUGIN_SRC:.c=.so}
+94
View File
@@ -0,0 +1,94 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <mosquitto.h>
#include <mosquitto_broker.h>
#include <mosquitto_plugin.h>
static int tick_callback(int event, void *event_data, void *user_data);
static int unpwd_check_callback(int event, void *event_data, void *user_data);
static mosquitto_plugin_id_t *plg_id;
static char *username = NULL;
static char *password = NULL;
static char *client_id = NULL;
static int auth_delay = -1;
int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
{
int i;
for(i=0; i<supported_version_count; i++){
if(supported_versions[i] == 5){
return 5;
}
}
return -1;
}
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, tick_callback, NULL, NULL);
mosquitto_callback_register(plg_id, MOSQ_EVT_BASIC_AUTH, unpwd_check_callback, NULL, NULL);
return MOSQ_ERR_SUCCESS;
}
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *auth_opts, int auth_opt_count)
{
free(username);
free(password);
free(client_id);
mosquitto_callback_unregister(plg_id, MOSQ_EVT_BASIC_AUTH, unpwd_check_callback, NULL);
mosquitto_callback_unregister(plg_id, MOSQ_EVT_TICK, tick_callback, NULL);
return MOSQ_ERR_SUCCESS;
}
static int tick_callback(int event, void *event_data, void *user_data)
{
if(auth_delay == 0){
if(client_id && username && password
&& !strcmp(username, "delayed-username") && !strcmp(password, "good")){
mosquitto_complete_basic_auth(client_id, MOSQ_ERR_SUCCESS);
}else{
mosquitto_complete_basic_auth(client_id, MOSQ_ERR_AUTH);
}
free(username);
free(password);
free(client_id);
username = NULL;
password = NULL;
client_id = NULL;
}else if(auth_delay > 0){
auth_delay--;
}
return MOSQ_ERR_SUCCESS;
}
static int unpwd_check_callback(int event, void *event_data, void *user_data)
{
struct mosquitto_evt_basic_auth *ed = event_data;
free(username);
free(password);
free(client_id);
if(ed->username){
username = strdup(ed->username);
}
if(ed->password){
password = strdup(ed->password);
}
client_id = strdup(mosquitto_client_id(ed->client));
/* Delay for arbitrary 10 ticks */
auth_delay = 10;
return MOSQ_ERR_AUTH_DELAYED;
}
+1
View File
@@ -167,6 +167,7 @@ tests = [
(1, './09-plugin-auth-unpwd-success.py'),
(1, './09-plugin-auth-v2-unpwd-fail.py'),
(1, './09-plugin-auth-v2-unpwd-success.py'),
(1, './09-plugin-delayed-auth.py'),
(1, './09-plugin-publish.py'),
(1, './09-pwfile-parse-invalid.py'),
+9 -2
View File
@@ -274,8 +274,15 @@ def to_string(packet):
return s
elif cmd == 0x20:
# CONNACK
(cmd, rl, resv, rc) = struct.unpack('!BBBB', packet)
return "CONNACK, rl="+str(rl)+", res="+str(resv)+", rc="+str(rc)
if len(packet) == 4:
(cmd, rl, resv, rc) = struct.unpack('!BBBB', packet)
return "CONNACK, rl="+str(rl)+", res="+str(resv)+", rc="+str(rc)
elif len(packet) == 5:
(cmd, rl, flags, reason_code, proplen) = struct.unpack('!BBBBB', packet)
return "CONNACK, rl="+str(rl)+", flags="+str(flags)+", rc="+str(reason_code)+", proplen="+str(proplen)
else:
return "CONNACK, (not decoded)"
elif cmd == 0x30:
# PUBLISH
dup = (packet0 & 0x08)>>3