mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-11-25 19:59:32 +01:00
Merge pull request #938 from david-cermak/feat/mosq_basic_auth
[mosq]: Add support for basic MQTT authentication
This commit is contained in:
28
.github/workflows/mosq__build.yml
vendored
28
.github/workflows/mosq__build.yml
vendored
@@ -77,6 +77,20 @@ jobs:
|
|||||||
- name: Run Test
|
- name: Run Test
|
||||||
working-directory: ${{ env.TEST_DIR }}
|
working-directory: ${{ env.TEST_DIR }}
|
||||||
run: |
|
run: |
|
||||||
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
eval "$(pyenv init --path)"
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
|
||||||
|
echo "Installing Python 3.12.6..."
|
||||||
|
pyenv install -s 3.12.6
|
||||||
|
fi
|
||||||
|
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
|
||||||
|
echo "Creating pyenv virtualenv 'myenv'..."
|
||||||
|
pyenv virtualenv 3.12.6 myenv
|
||||||
|
fi
|
||||||
|
pyenv activate myenv
|
||||||
|
python --version
|
||||||
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results
|
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results
|
||||||
unzip ci/artifacts.zip -d ci
|
unzip ci/artifacts.zip -d ci
|
||||||
for dir in `ls -d ci/build_*`; do
|
for dir in `ls -d ci/build_*`; do
|
||||||
@@ -180,6 +194,20 @@ jobs:
|
|||||||
- name: Run Test
|
- name: Run Test
|
||||||
working-directory: ${{ env.TEST_DIR }}
|
working-directory: ${{ env.TEST_DIR }}
|
||||||
run: |
|
run: |
|
||||||
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
eval "$(pyenv init --path)"
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
|
||||||
|
echo "Installing Python 3.12.6..."
|
||||||
|
pyenv install -s 3.12.6
|
||||||
|
fi
|
||||||
|
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
|
||||||
|
echo "Creating pyenv virtualenv 'myenv'..."
|
||||||
|
pyenv virtualenv 3.12.6 myenv
|
||||||
|
fi
|
||||||
|
pyenv activate myenv
|
||||||
|
python --version
|
||||||
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results "paho-mqtt<2" --upgrade
|
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results "paho-mqtt<2" --upgrade
|
||||||
unzip ci/artifacts.zip -d ci
|
unzip ci/artifacts.zip -d ci
|
||||||
for dir in `ls -d ci/build_*`; do
|
for dir in `ls -d ci/build_*`; do
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ if (CONFIG_MOSQ_ENABLE_SYS)
|
|||||||
endif()
|
endif()
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
||||||
|
|
||||||
|
# Enable linker wrapping for mosquitto_unpwd_check to allow connection callback interception
|
||||||
|
# without modifying upstream code
|
||||||
|
target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mosquitto_unpwd_check")
|
||||||
|
|
||||||
# Some mosquitto source unconditionally define `_GNU_SOURCE` which collides with IDF build system
|
# Some mosquitto source unconditionally define `_GNU_SOURCE` which collides with IDF build system
|
||||||
# producing warning: "_GNU_SOURCE" redefined
|
# producing warning: "_GNU_SOURCE" redefined
|
||||||
# This workarounds this issue by undefining the macro for the selected files
|
# This workarounds this issue by undefining the macro for the selected files
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
| Type | Name |
|
| Type | Name |
|
||||||
| ---: | :--- |
|
| ---: | :--- |
|
||||||
| struct | [**mosq\_broker\_config**](#struct-mosq_broker_config) <br>_Mosquitto configuration structure._ |
|
| struct | [**mosq\_broker\_config**](#struct-mosq_broker_config) <br>_Mosquitto configuration structure._ |
|
||||||
|
| typedef int(\* | [**mosq\_connect\_cb\_t**](#typedef-mosq_connect_cb_t) <br> |
|
||||||
| typedef void(\* | [**mosq\_message\_cb\_t**](#typedef-mosq_message_cb_t) <br> |
|
| typedef void(\* | [**mosq\_message\_cb\_t**](#typedef-mosq_message_cb_t) <br> |
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
@@ -35,6 +36,8 @@ ESP port of mosquittto supports only the options in this configuration structure
|
|||||||
|
|
||||||
Variables:
|
Variables:
|
||||||
|
|
||||||
|
- mosq\_connect\_cb\_t handle_connect_cb <br>On connect callback. If configured, user function is called whenever a client attempts to connect. The callback receives client\_id, username, password, and password length. Return 0 to accept the connection, non-zero to reject it.
|
||||||
|
|
||||||
- void(\* handle_message_cb <br>On message callback. If configured, user function is called whenever mosquitto processes a message.
|
- void(\* handle_message_cb <br>On message callback. If configured, user function is called whenever mosquitto processes a message.
|
||||||
|
|
||||||
- const char \* host <br>Address on which the broker is listening for connections
|
- const char \* host <br>Address on which the broker is listening for connections
|
||||||
@@ -43,6 +46,12 @@ Variables:
|
|||||||
|
|
||||||
- esp\_tls\_cfg\_server\_t \* tls_cfg <br>ESP-TLS configuration (if TLS transport used) Please refer to the ESP-TLS official documentation for more details on configuring the TLS options. You can open the respective docs with this idf.py command: `idf.py docs -sp api-reference/protocols/esp_tls.html`
|
- esp\_tls\_cfg\_server\_t \* tls_cfg <br>ESP-TLS configuration (if TLS transport used) Please refer to the ESP-TLS official documentation for more details on configuring the TLS options. You can open the respective docs with this idf.py command: `idf.py docs -sp api-reference/protocols/esp_tls.html`
|
||||||
|
|
||||||
|
### typedef `mosq_connect_cb_t`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef int(* mosq_connect_cb_t) (const char *client_id, const char *username, const char *password, int password_len);
|
||||||
|
```
|
||||||
|
|
||||||
### typedef `mosq_message_cb_t`
|
### typedef `mosq_message_cb_t`
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ menu "Example Configuration"
|
|||||||
If enabled, it runs a local mqtt client connecting
|
If enabled, it runs a local mqtt client connecting
|
||||||
to the same endpoint ans the broker listens to
|
to the same endpoint ans the broker listens to
|
||||||
|
|
||||||
|
config EXAMPLE_BROKER_USE_BASIC_AUTH
|
||||||
|
bool "Use basic authentication (username/password)"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
If enabled, the broker will require username and password
|
||||||
|
authentication. The example uses "testuser" / "testpass" as
|
||||||
|
credentials. The client will also use these credentials when
|
||||||
|
connecting to the broker.
|
||||||
|
|
||||||
config EXAMPLE_BROKER_WITH_TLS
|
config EXAMPLE_BROKER_WITH_TLS
|
||||||
bool "Use TLS"
|
bool "Use TLS"
|
||||||
default y
|
default y
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
@@ -14,6 +15,45 @@
|
|||||||
|
|
||||||
const static char *TAG = "mqtt_broker";
|
const static char *TAG = "mqtt_broker";
|
||||||
|
|
||||||
|
/* Basic auth credentials for the example */
|
||||||
|
#define EXAMPLE_USERNAME "testuser"
|
||||||
|
#define EXAMPLE_PASSWORD "testpass"
|
||||||
|
|
||||||
|
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
|
||||||
|
/* Connection callback to validate username/password */
|
||||||
|
static int example_connect_callback(const char *client_id, const char *username, const char *password, int password_len)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Connection attempt from client_id='%s', username='%s'", client_id, username ? username : "(none)");
|
||||||
|
|
||||||
|
/* Check if username is provided */
|
||||||
|
if (!username) {
|
||||||
|
ESP_LOGW(TAG, "Connection rejected: no username provided");
|
||||||
|
return 1; /* Reject connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if password is provided */
|
||||||
|
if (!password) {
|
||||||
|
ESP_LOGW(TAG, "Connection rejected: no password provided");
|
||||||
|
return 1; /* Reject connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate username */
|
||||||
|
if (strcmp(username, EXAMPLE_USERNAME) != 0) {
|
||||||
|
ESP_LOGW(TAG, "Connection rejected: invalid username '%s'", username);
|
||||||
|
return 1; /* Reject connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate password */
|
||||||
|
if (strcmp(password, EXAMPLE_PASSWORD) != 0) {
|
||||||
|
ESP_LOGW(TAG, "Connection rejected: invalid password");
|
||||||
|
return 1; /* Reject connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Connection accepted for client_id='%s', username='%s'", client_id, username);
|
||||||
|
return 0; /* Accept connection */
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH */
|
||||||
|
|
||||||
#if CONFIG_EXAMPLE_BROKER_WITH_TLS
|
#if CONFIG_EXAMPLE_BROKER_WITH_TLS
|
||||||
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
|
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
|
||||||
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
|
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
|
||||||
@@ -81,6 +121,10 @@ static void mqtt_app_start(struct mosq_broker_config *config)
|
|||||||
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
|
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
|
||||||
#endif
|
#endif
|
||||||
.broker.address.port = config->port,
|
.broker.address.port = config->port,
|
||||||
|
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
|
||||||
|
.credentials.username = "EXAMPLE_USERNAME",
|
||||||
|
.credentials.authentication.password = EXAMPLE_PASSWORD,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
|
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
|
||||||
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
||||||
@@ -95,7 +139,16 @@ void app_main(void)
|
|||||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
ESP_ERROR_CHECK(example_connect());
|
ESP_ERROR_CHECK(example_connect());
|
||||||
|
|
||||||
struct mosq_broker_config config = { .host = CONFIG_EXAMPLE_BROKER_HOST, .port = CONFIG_EXAMPLE_BROKER_PORT, .tls_cfg = NULL };
|
struct mosq_broker_config config = {
|
||||||
|
.host = CONFIG_EXAMPLE_BROKER_HOST,
|
||||||
|
.port = CONFIG_EXAMPLE_BROKER_PORT,
|
||||||
|
.tls_cfg = NULL,
|
||||||
|
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
|
||||||
|
.handle_connect_cb = example_connect_callback,
|
||||||
|
#else
|
||||||
|
.handle_connect_cb = NULL,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
#if CONFIG_EXAMPLE_BROKER_RUN_LOCAL_MQTT_CLIENT
|
#if CONFIG_EXAMPLE_BROKER_RUN_LOCAL_MQTT_CLIENT
|
||||||
mqtt_app_start(&config);
|
mqtt_app_start(&config);
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ void mosq_broker_stop(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern mosq_message_cb_t g_mosq_message_callback;
|
extern mosq_message_cb_t g_mosq_message_callback;
|
||||||
|
extern mosq_connect_cb_t g_mosq_connect_callback;
|
||||||
|
|
||||||
int mosq_broker_run(struct mosq_broker_config *broker_config)
|
int mosq_broker_run(struct mosq_broker_config *broker_config)
|
||||||
{
|
{
|
||||||
@@ -130,6 +131,9 @@ int mosq_broker_run(struct mosq_broker_config *broker_config)
|
|||||||
if (broker_config->handle_message_cb) {
|
if (broker_config->handle_message_cb) {
|
||||||
g_mosq_message_callback = broker_config->handle_message_cb;
|
g_mosq_message_callback = broker_config->handle_message_cb;
|
||||||
}
|
}
|
||||||
|
if (broker_config->handle_connect_cb) {
|
||||||
|
g_mosq_connect_callback = broker_config->handle_connect_cb;
|
||||||
|
}
|
||||||
|
|
||||||
db.config = &config;
|
db.config = &config;
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*
|
*
|
||||||
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileContributor: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*/
|
*/
|
||||||
|
#include <string.h>
|
||||||
#include "mosquitto_internal.h"
|
#include "mosquitto_internal.h"
|
||||||
#include "mosquitto_broker.h"
|
#include "mosquitto_broker.h"
|
||||||
#include "memory_mosq.h"
|
#include "memory_mosq.h"
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
#include "mosq_broker.h"
|
#include "mosq_broker.h"
|
||||||
|
|
||||||
mosq_message_cb_t g_mosq_message_callback = NULL;
|
mosq_message_cb_t g_mosq_message_callback = NULL;
|
||||||
|
mosq_connect_cb_t g_mosq_connect_callback = NULL;
|
||||||
|
|
||||||
int mosquitto_callback_register(
|
int mosquitto_callback_register(
|
||||||
mosquitto_plugin_id_t *identifier,
|
mosquitto_plugin_id_t *identifier,
|
||||||
@@ -51,3 +53,39 @@ int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store
|
|||||||
}
|
}
|
||||||
return MOSQ_ERR_SUCCESS;
|
return MOSQ_ERR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int __real_mosquitto_unpwd_check(struct mosquitto *context);
|
||||||
|
|
||||||
|
/* Wrapper function to intercept mosquitto_unpwd_check calls via linker wrapping */
|
||||||
|
int __wrap_mosquitto_unpwd_check(struct mosquitto *context)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int password_len = 0;
|
||||||
|
|
||||||
|
/* Call user's connect callback if set */
|
||||||
|
if (g_mosq_connect_callback) {
|
||||||
|
/* Extract password length if password is present.
|
||||||
|
* Note: MQTT passwords are binary data, but mosquitto stores them as null-terminated strings.
|
||||||
|
* If password contains null bytes, strlen() will not return the full length.
|
||||||
|
* This matches how mosquitto itself handles passwords in some security functions. */
|
||||||
|
if (context->password) {
|
||||||
|
password_len = (int)strlen(context->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call user callback */
|
||||||
|
rc = g_mosq_connect_callback(
|
||||||
|
context->id ? context->id : "",
|
||||||
|
context->username ? context->username : NULL,
|
||||||
|
context->password ? context->password : NULL,
|
||||||
|
password_len
|
||||||
|
);
|
||||||
|
|
||||||
|
/* If callback rejects (returns non-zero), return AUTH error immediately */
|
||||||
|
if (rc != 0) {
|
||||||
|
return MOSQ_ERR_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call the original function */
|
||||||
|
return __real_mosquitto_unpwd_check(context);
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ extern "C" {
|
|||||||
struct mosquitto__config;
|
struct mosquitto__config;
|
||||||
|
|
||||||
typedef void (*mosq_message_cb_t)(char *client, char *topic, char *data, int len, int qos, int retain);
|
typedef void (*mosq_message_cb_t)(char *client, char *topic, char *data, int len, int qos, int retain);
|
||||||
|
|
||||||
|
typedef int (*mosq_connect_cb_t)(const char *client_id, const char *username, const char *password, int password_len);
|
||||||
/**
|
/**
|
||||||
* @brief Mosquitto configuration structure
|
* @brief Mosquitto configuration structure
|
||||||
*
|
*
|
||||||
@@ -33,6 +35,11 @@ struct mosq_broker_config {
|
|||||||
* On message callback. If configured, user function is called
|
* On message callback. If configured, user function is called
|
||||||
* whenever mosquitto processes a message.
|
* whenever mosquitto processes a message.
|
||||||
*/
|
*/
|
||||||
|
mosq_connect_cb_t handle_connect_cb; /*!< On connect callback. If configured, user function is called
|
||||||
|
* whenever a client attempts to connect. The callback receives
|
||||||
|
* client_id, username, password, and password length. Return 0 to
|
||||||
|
* accept the connection, non-zero to reject it.
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user