mirror of
https://dev.iopsys.eu/feed/iopsys.git
synced 2025-12-10 07:44:50 +01:00
mosquitto-auth-shadow: Add per-user subnet-based access control to mosquitto auth plugin
- Implement whitelist/blacklist subnet filtering for MQTT users - Add support for CIDR notation (e.g., 192.168.1.0/24) - Parse subnet ACL files via auth_opt_subnet_acl_file option - Support multiple subnets per user (up to 32 allow + 32 deny rules) - Deny rules take precedence over allow rules - Localhost (127.0.0.1/::1) always allowed - IPv6 addresses gracefully deferred to other ACL handlers - Backward compatible: users without subnet rules are not affected - Configuration format: 'subnet allow|deny <username> <cidr>' - Integrates with existing shadow/PAM authentication and topic ACLs
This commit is contained in:
parent
9944917399
commit
ff24c2e1fa
2 changed files with 470 additions and 93 deletions
|
|
@ -14,7 +14,7 @@
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=mosquitto-auth-shadow
|
PKG_NAME:=mosquitto-auth-shadow
|
||||||
PKG_VERSION:=1.1.0
|
PKG_VERSION:=1.2.0
|
||||||
|
|
||||||
PKG_MAINTAINER:=Erik Karlsson <erik.karlsson@genexis.eu>
|
PKG_MAINTAINER:=Erik Karlsson <erik.karlsson@genexis.eu>
|
||||||
PKG_LICENSE:=EPL-2.0
|
PKG_LICENSE:=EPL-2.0
|
||||||
|
|
|
||||||
|
|
@ -12,142 +12,519 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <shadow.h>
|
#include <shadow.h>
|
||||||
#include <crypt.h>
|
#include <crypt.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <mosquitto.h>
|
#include <mosquitto.h>
|
||||||
#include <mosquitto_broker.h>
|
#include <mosquitto_broker.h>
|
||||||
#include <mosquitto_plugin.h>
|
#include <mosquitto_plugin.h>
|
||||||
|
|
||||||
#ifdef ENABLE_PAM_SUPPORT
|
#ifdef ENABLE_PAM_SUPPORT
|
||||||
#include <security/pam_appl.h>
|
#include <security/pam_appl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
static int pam_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
|
#define MAX_USERS 256
|
||||||
|
#define MAX_SUBNETS_PER_USER 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t network;
|
||||||
|
uint32_t netmask;
|
||||||
|
} subnet_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char username[64];
|
||||||
|
subnet_t allow_subnets[MAX_SUBNETS_PER_USER];
|
||||||
|
int allow_count;
|
||||||
|
subnet_t deny_subnets[MAX_SUBNETS_PER_USER];
|
||||||
|
int deny_count;
|
||||||
|
} user_acl_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
user_acl_t users[MAX_USERS];
|
||||||
|
int user_count;
|
||||||
|
mosquitto_plugin_id_t *identifier;
|
||||||
|
} plugin_data_t;
|
||||||
|
|
||||||
|
/* Parse CIDR notation (e.g., "192.168.1.0/24") */
|
||||||
|
static int parse_subnet(const char *cidr, subnet_t *subnet)
|
||||||
{
|
{
|
||||||
int i;
|
char ip_str[64];
|
||||||
const char *pass = (const char *)appdata_ptr;
|
char *slash;
|
||||||
|
int prefix_len;
|
||||||
|
struct in_addr addr;
|
||||||
|
|
||||||
*resp = calloc(num_msg, sizeof(struct pam_response));
|
strncpy(ip_str, cidr, sizeof(ip_str) - 1);
|
||||||
if (*resp == NULL) {
|
ip_str[sizeof(ip_str) - 1] = '\0';
|
||||||
mosquitto_log_printf(MOSQ_LOG_ERR, "pam failed to allocate buffer for validation");
|
|
||||||
return PAM_BUF_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pass == NULL)
|
slash = strchr(ip_str, '/');
|
||||||
return PAM_SUCCESS;
|
if (slash == NULL) {
|
||||||
|
/* No prefix length, assume /32 */
|
||||||
|
prefix_len = 32;
|
||||||
|
} else {
|
||||||
|
*slash = '\0';
|
||||||
|
prefix_len = atoi(slash + 1);
|
||||||
|
if (prefix_len < 0 || prefix_len > 32)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < num_msg; ++i) {
|
if (inet_pton(AF_INET, ip_str, &addr) != 1)
|
||||||
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
|
return -1;
|
||||||
(*resp)[i].resp = strdup(pass);
|
|
||||||
if ((*resp)[i].resp == NULL) {
|
|
||||||
for (int j = 0; j < i ; j++)
|
|
||||||
free((*resp)[j].resp);
|
|
||||||
|
|
||||||
free(*resp);
|
subnet->network = ntohl(addr.s_addr);
|
||||||
*resp = NULL;
|
subnet->netmask = prefix_len == 0 ? 0 : (~0U << (32 - prefix_len));
|
||||||
mosquitto_log_printf(MOSQ_LOG_ERR, "pam failed in strdup");
|
subnet->network &= subnet->netmask;
|
||||||
return PAM_BUF_ERR;
|
|
||||||
}
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return PAM_SUCCESS;
|
/* Check if IP is in subnet */
|
||||||
|
static int ip_in_subnet(uint32_t ip, const subnet_t *subnet)
|
||||||
|
{
|
||||||
|
return (ip & subnet->netmask) == subnet->network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if IP is in any subnet in the list */
|
||||||
|
static int ip_in_subnet_list(uint32_t ip, const subnet_t *subnets, int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (ip_in_subnet(ip, &subnets[i]))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find or create user ACL entry */
|
||||||
|
static user_acl_t* find_or_create_user_acl(plugin_data_t *pdata, const char *username)
|
||||||
|
{
|
||||||
|
user_acl_t *user;
|
||||||
|
|
||||||
|
/* Find existing user */
|
||||||
|
for (int i = 0; i < pdata->user_count; i++) {
|
||||||
|
if (strcmp(pdata->users[i].username, username) == 0)
|
||||||
|
return &pdata->users[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create new user if not found */
|
||||||
|
if (pdata->user_count >= MAX_USERS) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_ERR,
|
||||||
|
"subnet_acl: Max users exceeded");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
user = &pdata->users[pdata->user_count];
|
||||||
|
strncpy(user->username, username, sizeof(user->username) - 1);
|
||||||
|
user->username[sizeof(user->username) - 1] = '\0';
|
||||||
|
user->allow_count = 0;
|
||||||
|
user->deny_count = 0;
|
||||||
|
pdata->user_count++;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse subnet ACL file
|
||||||
|
* Format:
|
||||||
|
* # Comment lines
|
||||||
|
* subnet allow <username> <cidr>
|
||||||
|
* subnet deny <username> <cidr>
|
||||||
|
*/
|
||||||
|
static int load_subnet_acl_config(plugin_data_t *pdata, const char *config_file)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char line[512];
|
||||||
|
int line_num = 0;
|
||||||
|
|
||||||
|
if (config_file == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_INFO,
|
||||||
|
"subnet_acl: No subnet ACL file specified, subnet filtering disabled");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen(config_file, "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Could not open subnet ACL file %s, subnet filtering disabled",
|
||||||
|
config_file);
|
||||||
|
return 0; /* Non-fatal */
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata->user_count = 0;
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||||
|
char *token, *saveptr;
|
||||||
|
char *action, *username, *cidr;
|
||||||
|
user_acl_t *user;
|
||||||
|
subnet_t subnet;
|
||||||
|
|
||||||
|
line_num++;
|
||||||
|
|
||||||
|
/* Remove newline and comments */
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
char *comment = strchr(line, '#');
|
||||||
|
if (comment)
|
||||||
|
*comment = '\0';
|
||||||
|
|
||||||
|
/* Trim leading whitespace */
|
||||||
|
char *line_start = line;
|
||||||
|
while (*line_start == ' ' || *line_start == '\t')
|
||||||
|
line_start++;
|
||||||
|
|
||||||
|
/* Skip empty lines */
|
||||||
|
if (*line_start == '\0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Parse: subnet allow|deny <username> <cidr> */
|
||||||
|
token = strtok_r(line_start, " \t", &saveptr);
|
||||||
|
if (token == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Must start with "subnet" */
|
||||||
|
if (strcmp(token, "subnet") != 0) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Unknown directive '%s' at line %d (expected 'subnet')",
|
||||||
|
token, line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get allow/deny */
|
||||||
|
action = strtok_r(NULL, " \t", &saveptr);
|
||||||
|
if (action == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Missing allow/deny at line %d", line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(action, "allow") != 0 && strcmp(action, "deny") != 0) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Unknown action '%s' at line %d (use 'allow' or 'deny')",
|
||||||
|
action, line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get username */
|
||||||
|
username = strtok_r(NULL, " \t", &saveptr);
|
||||||
|
if (username == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Missing username at line %d", line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get CIDR */
|
||||||
|
cidr = strtok_r(NULL, " \t", &saveptr);
|
||||||
|
if (cidr == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Missing CIDR at line %d", line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse subnet */
|
||||||
|
if (parse_subnet(cidr, &subnet) != 0) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Invalid CIDR '%s' at line %d", cidr, line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find or create user */
|
||||||
|
user = find_or_create_user_acl(pdata, username);
|
||||||
|
if (user == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_ERR,
|
||||||
|
"subnet_acl: Failed to create user at line %d", line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add to appropriate list */
|
||||||
|
if (strcmp(action, "allow") == 0) {
|
||||||
|
if (user->allow_count >= MAX_SUBNETS_PER_USER) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Max allow subnets exceeded for user '%s' at line %d",
|
||||||
|
user->username, line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
user->allow_subnets[user->allow_count] = subnet;
|
||||||
|
user->allow_count++;
|
||||||
|
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_DEBUG,
|
||||||
|
"subnet_acl: User '%s' allow subnet %s",
|
||||||
|
user->username, cidr);
|
||||||
|
|
||||||
|
} else { /* deny */
|
||||||
|
if (user->deny_count >= MAX_SUBNETS_PER_USER) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Max deny subnets exceeded for user '%s' at line %d",
|
||||||
|
user->username, line_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
user->deny_subnets[user->deny_count] = subnet;
|
||||||
|
user->deny_count++;
|
||||||
|
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_DEBUG,
|
||||||
|
"subnet_acl: User '%s' deny subnet %s",
|
||||||
|
user->username, cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
/* Log summary */
|
||||||
|
for (int i = 0; i < pdata->user_count; i++) {
|
||||||
|
user_acl_t *user = &pdata->users[i];
|
||||||
|
if (user->allow_count > 0 || user->deny_count > 0) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_INFO,
|
||||||
|
"subnet_acl: User '%s' has %d allow and %d deny subnet rules",
|
||||||
|
user->username, user->allow_count, user->deny_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_NOTICE,
|
||||||
|
"subnet_acl: Loaded subnet restrictions for %d user(s)", pdata->user_count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find user ACL entry */
|
||||||
|
static const user_acl_t* find_user_acl(const plugin_data_t *pdata, const char *username)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < pdata->user_count; i++) {
|
||||||
|
if (strcmp(pdata->users[i].username, username) == 0)
|
||||||
|
return &pdata->users[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ACL check for subnet validation */
|
||||||
|
static int acl_check_callback(int event, void *event_data, void *userdata)
|
||||||
|
{
|
||||||
|
struct mosquitto_evt_acl_check *ed = event_data;
|
||||||
|
plugin_data_t *pdata = userdata;
|
||||||
|
const user_acl_t *user_acl;
|
||||||
|
const char *client_address;
|
||||||
|
const char *username;
|
||||||
|
struct in_addr addr;
|
||||||
|
uint32_t client_ip;
|
||||||
|
|
||||||
|
/* Skip if no subnet config loaded */
|
||||||
|
if (pdata == NULL || pdata->user_count == 0) {
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get username from client */
|
||||||
|
username = mosquitto_client_username(ed->client);
|
||||||
|
|
||||||
|
/* Skip anonymous users */
|
||||||
|
if (username == NULL) {
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find user's subnet ACL */
|
||||||
|
user_acl = find_user_acl(pdata, username);
|
||||||
|
|
||||||
|
/* If user not in config or has no subnet rules, allow */
|
||||||
|
if (user_acl == NULL || (user_acl->allow_count == 0 && user_acl->deny_count == 0)) {
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get client IP address */
|
||||||
|
client_address = mosquitto_client_address(ed->client);
|
||||||
|
if (client_address == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
||||||
|
"subnet_acl: Could not get client address for user '%s'", username);
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip localhost checks - always allow */
|
||||||
|
if (strcmp(client_address, "127.0.0.1") == 0 || strcmp(client_address, "::1") == 0) {
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse client IP */
|
||||||
|
if (inet_pton(AF_INET, client_address, &addr) != 1) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_DEBUG,
|
||||||
|
"subnet_acl: Non-IPv4 address '%s' for user '%s', allowing",
|
||||||
|
client_address, username);
|
||||||
|
/* For IPv6 or parse errors, defer to other plugins */
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_ip = ntohl(addr.s_addr);
|
||||||
|
|
||||||
|
/* Check deny list first - deny takes precedence */
|
||||||
|
if (user_acl->deny_count > 0) {
|
||||||
|
if (ip_in_subnet_list(client_ip, user_acl->deny_subnets, user_acl->deny_count)) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_NOTICE,
|
||||||
|
"subnet_acl: User '%s' from %s DENIED (matches deny rule)",
|
||||||
|
username, client_address);
|
||||||
|
return MOSQ_ERR_ACL_DENIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there are allow rules, IP must match one of them */
|
||||||
|
if (user_acl->allow_count > 0) {
|
||||||
|
if (ip_in_subnet_list(client_ip, user_acl->allow_subnets, user_acl->allow_count)) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_DEBUG,
|
||||||
|
"subnet_acl: User '%s' from %s allowed (matches allow rule)",
|
||||||
|
username, client_address);
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
} else {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_NOTICE,
|
||||||
|
"subnet_acl: User '%s' from %s DENIED (no matching allow rule)",
|
||||||
|
username, client_address);
|
||||||
|
return MOSQ_ERR_ACL_DENIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No subnet rules for this user - allow */
|
||||||
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_PAM_SUPPORT
|
||||||
|
static int pam_conversation(int num_msg, const struct pam_message **msg,
|
||||||
|
struct pam_response **resp, void *appdata_ptr)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const char *pass = (const char *)appdata_ptr;
|
||||||
|
*resp = calloc(num_msg, sizeof(struct pam_response));
|
||||||
|
if (*resp == NULL) {
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_ERR, "pam failed to allocate buffer for validation");
|
||||||
|
return PAM_BUF_ERR;
|
||||||
|
}
|
||||||
|
if (pass == NULL)
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
for (i = 0; i < num_msg; ++i) {
|
||||||
|
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
|
||||||
|
(*resp)[i].resp = strdup(pass);
|
||||||
|
if ((*resp)[i].resp == NULL) {
|
||||||
|
for (int j = 0; j < i ; j++)
|
||||||
|
free((*resp)[j].resp);
|
||||||
|
free(*resp);
|
||||||
|
*resp = NULL;
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_ERR, "pam failed in strdup");
|
||||||
|
return PAM_BUF_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_pam_auth_callback(struct mosquitto_evt_basic_auth *ed)
|
static int process_pam_auth_callback(struct mosquitto_evt_basic_auth *ed)
|
||||||
{
|
{
|
||||||
struct pam_conv conv;
|
struct pam_conv conv;
|
||||||
int retval;
|
int retval;
|
||||||
pam_handle_t *pamh = NULL;
|
pam_handle_t *pamh = NULL;
|
||||||
|
conv.conv = pam_conversation;
|
||||||
conv.conv = pam_conversation;
|
conv.appdata_ptr = (void *)ed->password;
|
||||||
conv.appdata_ptr = (void *)ed->password;
|
retval = pam_start("mosquitto", ed->username, &conv, &pamh);
|
||||||
|
if (retval != PAM_SUCCESS) {
|
||||||
retval = pam_start("mosquitto", ed->username, &conv, &pamh);
|
mosquitto_log_printf(MOSQ_LOG_ERR, "pam start failed: %s", pam_strerror(pamh, retval));
|
||||||
if (retval != PAM_SUCCESS) {
|
return MOSQ_ERR_AUTH;
|
||||||
mosquitto_log_printf(MOSQ_LOG_ERR, "pam start failed: %s", pam_strerror(pamh, retval));
|
}
|
||||||
return MOSQ_ERR_AUTH;
|
retval = pam_authenticate(pamh, 0);
|
||||||
}
|
pam_end(pamh, retval);
|
||||||
|
if (retval == PAM_SUCCESS) {
|
||||||
retval = pam_authenticate(pamh, 0);
|
mosquitto_log_printf(MOSQ_LOG_NOTICE, "pam user [%s] logged in", ed->username);
|
||||||
pam_end(pamh, retval);
|
return MOSQ_ERR_SUCCESS;
|
||||||
if (retval == PAM_SUCCESS) {
|
}
|
||||||
mosquitto_log_printf(MOSQ_LOG_NOTICE, "pam user [%s] logged in", ed->username);
|
mosquitto_log_printf(MOSQ_LOG_NOTICE, "pam user [%s] failed authentication, err [%s]",
|
||||||
return MOSQ_ERR_SUCCESS;
|
ed->username, pam_strerror(pamh, retval));
|
||||||
}
|
return MOSQ_ERR_AUTH;
|
||||||
|
|
||||||
mosquitto_log_printf(MOSQ_LOG_NOTICE, "pam user [%s] failed authentication, err [%s]", ed->username, pam_strerror(pamh, retval));
|
|
||||||
return MOSQ_ERR_AUTH;
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static int process_shadow_auth_callback(struct mosquitto_evt_basic_auth *ed)
|
static int process_shadow_auth_callback(struct mosquitto_evt_basic_auth *ed)
|
||||||
{
|
{
|
||||||
struct spwd spbuf, *sp = NULL;
|
struct spwd spbuf, *sp = NULL;
|
||||||
char buf[256];
|
char buf[256];
|
||||||
struct crypt_data data;
|
struct crypt_data data;
|
||||||
char *hash;
|
char *hash;
|
||||||
|
getspnam_r(ed->username, &spbuf, buf, sizeof(buf), &sp);
|
||||||
getspnam_r(ed->username, &spbuf, buf, sizeof(buf), &sp);
|
if (sp == NULL || sp->sp_pwdp == NULL)
|
||||||
|
return MOSQ_ERR_AUTH;
|
||||||
if (sp == NULL || sp->sp_pwdp == NULL)
|
/* Empty string as hash means password is not required */
|
||||||
return MOSQ_ERR_AUTH;
|
if (sp->sp_pwdp[0] == 0)
|
||||||
|
return MOSQ_ERR_SUCCESS;
|
||||||
/* Empty string as hash means password is not required */
|
if (ed->password == NULL)
|
||||||
if (sp->sp_pwdp[0] == 0)
|
return MOSQ_ERR_AUTH;
|
||||||
return MOSQ_ERR_SUCCESS;
|
memset(&data, 0, sizeof(data));
|
||||||
|
hash = crypt_r(ed->password, sp->sp_pwdp, &data);
|
||||||
if (ed->password == NULL)
|
if (hash == NULL)
|
||||||
return MOSQ_ERR_AUTH;
|
return MOSQ_ERR_AUTH;
|
||||||
|
if (strcmp(hash, sp->sp_pwdp) == 0)
|
||||||
memset(&data, 0, sizeof(data));
|
return MOSQ_ERR_SUCCESS;
|
||||||
hash = crypt_r(ed->password, sp->sp_pwdp, &data);
|
return MOSQ_ERR_AUTH;
|
||||||
|
|
||||||
if (hash == NULL)
|
|
||||||
return MOSQ_ERR_AUTH;
|
|
||||||
|
|
||||||
if (strcmp(hash, sp->sp_pwdp) == 0)
|
|
||||||
return MOSQ_ERR_SUCCESS;
|
|
||||||
|
|
||||||
return MOSQ_ERR_AUTH;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int basic_auth_callback(int event, void *event_data, void *userdata)
|
static int basic_auth_callback(int event, void *event_data, void *userdata)
|
||||||
{
|
{
|
||||||
struct mosquitto_evt_basic_auth *ed = event_data;
|
struct mosquitto_evt_basic_auth *ed = event_data;
|
||||||
|
/* Let other plugins or broker decide about anonymous login */
|
||||||
/* Let other plugins or broker decide about anonymous login */
|
if (ed->username == NULL)
|
||||||
if (ed->username == NULL)
|
return MOSQ_ERR_PLUGIN_DEFER;
|
||||||
return MOSQ_ERR_PLUGIN_DEFER;
|
|
||||||
|
|
||||||
#ifdef ENABLE_PAM_SUPPORT
|
#ifdef ENABLE_PAM_SUPPORT
|
||||||
return process_pam_auth_callback(ed);
|
return process_pam_auth_callback(ed);
|
||||||
#else
|
#else
|
||||||
return process_shadow_auth_callback(ed);
|
return process_shadow_auth_callback(ed);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int mosquitto_plugin_version(int supported_version_count,
|
int mosquitto_plugin_version(int supported_version_count,
|
||||||
const int *supported_versions)
|
const int *supported_versions)
|
||||||
{
|
{
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier,
|
int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier,
|
||||||
void **user_data,
|
void **user_data,
|
||||||
struct mosquitto_opt *opts, int opt_count)
|
struct mosquitto_opt *opts, int opt_count)
|
||||||
{
|
{
|
||||||
*user_data = identifier;
|
plugin_data_t *pdata;
|
||||||
|
const char *config_file = NULL;
|
||||||
|
|
||||||
return mosquitto_callback_register(identifier, MOSQ_EVT_BASIC_AUTH,
|
/* Find subnet config file option */
|
||||||
basic_auth_callback, NULL, NULL);
|
for (int i = 0; i < opt_count; i++) {
|
||||||
|
if (strcmp(opts[i].key, "subnet_acl_file") == 0) {
|
||||||
|
config_file = opts[i].value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdata = calloc(1, sizeof(plugin_data_t));
|
||||||
|
if (pdata == NULL)
|
||||||
|
return MOSQ_ERR_NOMEM;
|
||||||
|
|
||||||
|
pdata->identifier = identifier;
|
||||||
|
*user_data = pdata;
|
||||||
|
|
||||||
|
/* Load subnet ACL configuration */
|
||||||
|
if (load_subnet_acl_config(pdata, config_file) != 0) {
|
||||||
|
free(pdata);
|
||||||
|
return MOSQ_ERR_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register both authentication and ACL callbacks */
|
||||||
|
mosquitto_callback_register(identifier, MOSQ_EVT_BASIC_AUTH,
|
||||||
|
basic_auth_callback, NULL, pdata);
|
||||||
|
mosquitto_callback_register(identifier, MOSQ_EVT_ACL_CHECK,
|
||||||
|
acl_check_callback, NULL, pdata);
|
||||||
|
|
||||||
|
mosquitto_log_printf(MOSQ_LOG_INFO,
|
||||||
|
"subnet_acl: Plugin initialized with %d user(s)", pdata->user_count);
|
||||||
|
|
||||||
|
return MOSQ_ERR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mosquitto_plugin_cleanup(void *user_data,
|
int mosquitto_plugin_cleanup(void *user_data,
|
||||||
struct mosquitto_opt *opts, int opt_count)
|
struct mosquitto_opt *opts, int opt_count)
|
||||||
{
|
{
|
||||||
mosquitto_plugin_id_t *identifier = user_data;
|
plugin_data_t *pdata = user_data;
|
||||||
|
|
||||||
return mosquitto_callback_unregister(identifier, MOSQ_EVT_BASIC_AUTH,
|
if (pdata) {
|
||||||
basic_auth_callback, NULL);
|
mosquitto_callback_unregister(pdata->identifier, MOSQ_EVT_BASIC_AUTH,
|
||||||
|
basic_auth_callback, NULL);
|
||||||
|
mosquitto_callback_unregister(pdata->identifier, MOSQ_EVT_ACL_CHECK,
|
||||||
|
acl_check_callback, NULL);
|
||||||
|
free(pdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MOSQ_ERR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue