mirror of
https://github.com/eclipse-mosquitto/mosquitto.git
synced 2026-04-19 19:46:01 +02:00
356 lines
7.7 KiB
C
356 lines
7.7 KiB
C
/*
|
|
Copyright (c) 2011-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 BSD-3-Clause
|
|
|
|
Contributors:
|
|
Roger Light - initial implementation and documentation.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <utlist.h>
|
|
|
|
#include "mosquitto.h"
|
|
#include "acl_file.h"
|
|
|
|
|
|
static int acl__add_to_user(struct acl__user *acl_user, const char *topic, int access)
|
|
{
|
|
struct acl__entry *acl;
|
|
|
|
acl = mosquitto_calloc(1, sizeof(struct acl__entry));
|
|
if(!acl){
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
acl->access = access;
|
|
|
|
acl->topic = mosquitto_strdup(topic);
|
|
if(!acl->topic){
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
|
|
/* Add acl to user acl list */
|
|
if(access == MOSQ_ACL_NONE){
|
|
/* Put "deny" acls at front of the list */
|
|
DL_PREPEND(acl_user->acl, acl);
|
|
}else{
|
|
DL_APPEND(acl_user->acl, acl);
|
|
}
|
|
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
|
|
static struct acl__user *acl__find_or_create_user(struct acl_file_data *data, const char *user, unsigned user_hashv)
|
|
{
|
|
if(user == NULL){
|
|
return &data->acl_anon;
|
|
}else{
|
|
struct acl__user *acl_user=NULL;
|
|
|
|
HASH_FIND_BYHASHVALUE(hh, data->acl_users, user, strlen(user), user_hashv, acl_user);
|
|
|
|
if(!acl_user){
|
|
acl_user = mosquitto_calloc(1, sizeof(struct acl__user));
|
|
if(!acl_user){
|
|
return NULL;
|
|
}
|
|
if(user){
|
|
acl_user->username = mosquitto_strdup(user);
|
|
if(!acl_user->username){
|
|
mosquitto_FREE(acl_user);
|
|
return NULL;
|
|
}
|
|
}
|
|
HASH_ADD_KEYPTR(hh, data->acl_users, acl_user->username, strlen(acl_user->username), acl_user);
|
|
}
|
|
|
|
return acl_user;
|
|
}
|
|
}
|
|
|
|
|
|
static int acl__add(struct acl_file_data *data, const char *user, unsigned user_hashv, const char *topic, int access)
|
|
{
|
|
struct acl__user *acl_user=NULL;
|
|
|
|
if(!data || !topic){
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
|
|
acl_user = acl__find_or_create_user(data, user, user_hashv);
|
|
if(!acl_user){
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
|
|
return acl__add_to_user(acl_user, topic, access);
|
|
}
|
|
|
|
|
|
static int acl__add_pattern(struct acl_file_data *data, const char *topic, int access)
|
|
{
|
|
struct acl__entry *acl, *acl_tail;
|
|
char *local_topic;
|
|
char *s;
|
|
|
|
if(!data| !topic){
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
|
|
local_topic = mosquitto_strdup(topic);
|
|
if(!local_topic){
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
|
|
acl = mosquitto_malloc(sizeof(struct acl__entry));
|
|
if(!acl){
|
|
mosquitto_FREE(local_topic);
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
acl->access = access;
|
|
acl->topic = local_topic;
|
|
acl->next = NULL;
|
|
|
|
acl->ccount = 0;
|
|
s = local_topic;
|
|
while(s){
|
|
s = strstr(s, "%c");
|
|
if(s){
|
|
acl->ccount++;
|
|
s+=2;
|
|
}
|
|
}
|
|
|
|
acl->ucount = 0;
|
|
s = local_topic;
|
|
while(s){
|
|
s = strstr(s, "%u");
|
|
if(s){
|
|
acl->ucount++;
|
|
s+=2;
|
|
}
|
|
}
|
|
|
|
if(acl->ccount == 0 && acl->ucount == 0){
|
|
mosquitto_log_printf(MOSQ_LOG_WARNING,
|
|
"Warning: ACL pattern '%s' does not contain '%%c' or '%%u'.",
|
|
topic);
|
|
}
|
|
|
|
if(data->acl_patterns){
|
|
acl_tail = data->acl_patterns;
|
|
if(access == MOSQ_ACL_NONE){
|
|
/* Put "deny" acls at front of the list */
|
|
acl->next = acl_tail;
|
|
data->acl_patterns = acl;
|
|
}else{
|
|
while(acl_tail->next){
|
|
acl_tail = acl_tail->next;
|
|
}
|
|
acl_tail->next = acl;
|
|
}
|
|
}else{
|
|
data->acl_patterns = acl;
|
|
}
|
|
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
|
|
int acl_file__parse(struct acl_file_data *data)
|
|
{
|
|
FILE *aclfptr = NULL;
|
|
char *token;
|
|
char *user = NULL;
|
|
char *topic;
|
|
char *access_s;
|
|
int access;
|
|
int rc = MOSQ_ERR_SUCCESS;
|
|
size_t slen;
|
|
int topic_pattern;
|
|
char *saveptr = NULL;
|
|
char *buf = NULL;
|
|
int buflen = 256;
|
|
unsigned user_hashv = 0;
|
|
|
|
if(!data){
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
if(!data->acl_file){
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
buf = mosquitto_calloc((size_t)buflen, 1);
|
|
if(buf == NULL){
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Out of memory.");
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
|
|
aclfptr = mosquitto_fopen(data->acl_file, "rt", true);
|
|
if(!aclfptr){
|
|
mosquitto_FREE(buf);
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Unable to open acl_file \"%s\".", data->acl_file);
|
|
return MOSQ_ERR_UNKNOWN;
|
|
}
|
|
|
|
/* topic [read|write] <topic>
|
|
* user <user>
|
|
*/
|
|
|
|
while(mosquitto_fgets(&buf, &buflen, aclfptr)){
|
|
slen = strlen(buf);
|
|
while(slen > 0 && isspace((unsigned char)buf[slen-1])){
|
|
buf[slen-1] = '\0';
|
|
slen = strlen(buf);
|
|
}
|
|
if(buf[0] == '#'){
|
|
continue;
|
|
}
|
|
token = strtok_r(buf, " ", &saveptr);
|
|
if(token){
|
|
if(!strcmp(token, "topic") || !strcmp(token, "pattern")){
|
|
if(!strcmp(token, "topic")){
|
|
topic_pattern = 0;
|
|
}else{
|
|
topic_pattern = 1;
|
|
}
|
|
|
|
access_s = strtok_r(NULL, " ", &saveptr);
|
|
if(!access_s){
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Empty topic in acl_file \"%s\".", data->acl_file);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
token = strtok_r(NULL, "", &saveptr);
|
|
if(token){
|
|
topic = mosquitto_trimblanks(token);
|
|
}else{
|
|
topic = access_s;
|
|
access_s = NULL;
|
|
}
|
|
if(access_s){
|
|
if(!strcmp(access_s, "read")){
|
|
access = MOSQ_ACL_READ;
|
|
}else if(!strcmp(access_s, "write")){
|
|
access = MOSQ_ACL_WRITE;
|
|
}else if(!strcmp(access_s, "readwrite")){
|
|
access = MOSQ_ACL_READ | MOSQ_ACL_WRITE;
|
|
}else if(!strcmp(access_s, "deny")){
|
|
access = MOSQ_ACL_NONE;
|
|
}else{
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid topic access type \"%s\" in acl_file \"%s\".", access_s, data->acl_file);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
}else{
|
|
access = MOSQ_ACL_READ | MOSQ_ACL_WRITE;
|
|
}
|
|
rc = mosquitto_sub_topic_check(topic);
|
|
if(rc != MOSQ_ERR_SUCCESS){
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid ACL topic \"%s\" in acl_file \"%s\".", topic, data->acl_file);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
|
|
if(topic_pattern == 0){
|
|
rc = acl__add(data, user, user_hashv, topic, access);
|
|
}else{
|
|
rc = acl__add_pattern(data, topic, access);
|
|
}
|
|
if(rc){
|
|
break;
|
|
}
|
|
}else if(!strcmp(token, "user")){
|
|
token = strtok_r(NULL, "", &saveptr);
|
|
if(token){
|
|
token = mosquitto_trimblanks(token);
|
|
if(slen == 0){
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Missing username in acl_file \"%s\".", data->acl_file);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
mosquitto_FREE(user);
|
|
user = mosquitto_strdup(token);
|
|
if(!user){
|
|
rc = MOSQ_ERR_NOMEM;
|
|
break;
|
|
}
|
|
HASH_VALUE(user, strlen(user), user_hashv);
|
|
}else{
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Missing username in acl_file \"%s\".", data->acl_file);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
}else{
|
|
mosquitto_log_printf(MOSQ_LOG_ERR, "Error: Invalid line in acl_file \"%s\": %s.", data->acl_file, buf);
|
|
rc = MOSQ_ERR_INVAL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mosquitto_FREE(buf);
|
|
mosquitto_FREE(user);
|
|
fclose(aclfptr);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static void acl__free_entries(struct acl__entry *entry)
|
|
{
|
|
while(entry){
|
|
struct acl__entry *next = entry->next;
|
|
|
|
mosquitto_FREE(entry->topic);
|
|
mosquitto_FREE(entry);
|
|
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
|
|
void acl_file__cleanup(struct acl_file_data *data)
|
|
{
|
|
struct acl__user *user, *user_tmp;
|
|
|
|
HASH_ITER(hh, data->acl_users, user, user_tmp){
|
|
HASH_DELETE(hh, data->acl_users, user);
|
|
mosquitto_FREE(user->username);
|
|
acl__free_entries(user->acl);
|
|
mosquitto_FREE(user);
|
|
}
|
|
|
|
acl__free_entries(data->acl_anon.acl);
|
|
data->acl_anon.acl = NULL;
|
|
|
|
acl__free_entries(data->acl_patterns);
|
|
data->acl_patterns = NULL;
|
|
}
|
|
|
|
|
|
int acl_file__reload(int event, void *event_data, void *userdata)
|
|
{
|
|
struct acl_file_data *data = userdata;
|
|
|
|
UNUSED(event);
|
|
UNUSED(event_data);
|
|
|
|
acl_file__cleanup(data);
|
|
return acl_file__parse(data);
|
|
}
|