Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/mosq__build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ jobs:
- name: Run Test
working-directory: ${{ env.TEST_DIR }}
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
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
Expand Down Expand Up @@ -180,6 +194,20 @@ jobs:
- name: Run Test
working-directory: ${{ env.TEST_DIR }}
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
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
Expand Down
4 changes: 4 additions & 0 deletions components/mosquitto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ if (CONFIG_MOSQ_ENABLE_SYS)
endif()
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
# producing warning: "_GNU_SOURCE" redefined
# This workarounds this issue by undefining the macro for the selected files
Expand Down
9 changes: 9 additions & 0 deletions components/mosquitto/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| Type | Name |
| ---: | :--- |
| 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> |

## Functions
Expand All @@ -35,6 +36,8 @@ ESP port of mosquittto supports only the options in this configuration structure

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.

- const char \* host <br>Address on which the broker is listening for connections
Expand All @@ -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`

### 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`

```c
Expand Down
9 changes: 9 additions & 0 deletions components/mosquitto/examples/broker/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ menu "Example Configuration"
If enabled, it runs a local mqtt client connecting
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
bool "Use TLS"
default y
Expand Down
57 changes: 55 additions & 2 deletions components/mosquitto/examples/broker/main/example_broker.c
Original file line number Diff line number Diff line change
@@ -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
*/
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
Expand All @@ -14,6 +15,45 @@

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
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
Expand Down Expand Up @@ -81,6 +121,10 @@ static void mqtt_app_start(struct mosq_broker_config *config)
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
#endif
.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_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
Expand All @@ -95,7 +139,16 @@ void app_main(void)
ESP_ERROR_CHECK(esp_event_loop_create_default());
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
mqtt_app_start(&config);
Expand Down
4 changes: 4 additions & 0 deletions components/mosquitto/port/broker.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void mosq_broker_stop(void)
}

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)
{
Expand Down Expand Up @@ -130,6 +131,9 @@ int mosq_broker_run(struct mosq_broker_config *broker_config)
if (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;

Expand Down
40 changes: 39 additions & 1 deletion components/mosquitto/port/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
*
* 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_broker.h"
#include "memory_mosq.h"
Expand All @@ -16,6 +17,7 @@
#include "mosq_broker.h"

mosq_message_cb_t g_mosq_message_callback = NULL;
mosq_connect_cb_t g_mosq_connect_callback = NULL;

int mosquitto_callback_register(
mosquitto_plugin_id_t *identifier,
Expand Down Expand Up @@ -51,3 +53,39 @@ int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store
}
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);
}
7 changes: 7 additions & 0 deletions components/mosquitto/port/include/mosq_broker.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ extern "C" {
struct mosquitto__config;

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
*
Expand All @@ -33,6 +35,11 @@ struct mosq_broker_config {
* On message callback. If configured, user function is called
* 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.
*/
};

/**
Expand Down