mosquitto/lib/util_topic.c

680 lines
15 KiB
C
Raw Normal View History

/*
2021-11-03 23:08:24 +01:00
Copyright (c) 2009-2021 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
2020-11-25 18:34:21 +01:00
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
2020-11-25 18:34:21 +01:00
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
2020-12-01 19:21:59 +01:00
Contributors:
Roger Light - initial implementation and documentation.
*/
#include "config.h"
#include <assert.h>
#include <string.h>
#ifdef WIN32
# include <winsock2.h>
# include <aclapi.h>
# include <io.h>
# include <lmcons.h>
#else
# include <sys/stat.h>
#endif
#ifdef WITH_BROKER
#include "mosquitto_broker_internal.h"
#endif
#include "mosquitto.h"
#include "memory_mosq.h"
#include "net_mosq.h"
#include "send_mosq.h"
#include "time_mosq.h"
#include "tls_mosq.h"
#include "util_mosq.h"
/* Check that a topic used for publishing is valid.
* Search for + or # in a topic. Return MOSQ_ERR_INVAL if found.
* Also returns MOSQ_ERR_INVAL if the topic string is too long.
* Returns MOSQ_ERR_SUCCESS if everything is fine.
*/
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_pub_topic_check(const char *str)
{
int len = 0;
#ifdef WITH_BROKER
int hier_count = 0;
#endif
if(str == NULL){
return MOSQ_ERR_INVAL;
}
while(str && str[0]){
if(str[0] == '+' || str[0] == '#'){
return MOSQ_ERR_INVAL;
}
#ifdef WITH_BROKER
else if(str[0] == '/'){
hier_count++;
}
#endif
len++;
str = &str[1];
}
if(len > 65535) return MOSQ_ERR_INVAL;
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif
return MOSQ_ERR_SUCCESS;
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_pub_topic_check2(const char *str, size_t len)
{
size_t i;
#ifdef WITH_BROKER
int hier_count = 0;
#endif
if(str == NULL || len > 65535){
return MOSQ_ERR_INVAL;
}
for(i=0; i<len; i++){
if(str[i] == '+' || str[i] == '#'){
return MOSQ_ERR_INVAL;
}
#ifdef WITH_BROKER
else if(str[i] == '/'){
hier_count++;
}
#endif
}
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif
return MOSQ_ERR_SUCCESS;
}
/* Check that a topic used for subscriptions is valid.
* Search for + or # in a topic, check they aren't in invalid positions such as
* foo/#/bar, foo/+bar or foo/bar#.
* Return MOSQ_ERR_INVAL if invalid position found.
* Also returns MOSQ_ERR_INVAL if the topic string is too long.
* Returns MOSQ_ERR_SUCCESS if everything is fine.
*/
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_sub_topic_check(const char *str)
{
char c = '\0';
int len = 0;
#ifdef WITH_BROKER
int hier_count = 0;
#endif
if(str == NULL){
return MOSQ_ERR_INVAL;
}
while(str[0]){
if(str[0] == '+'){
if((c != '\0' && c != '/') || (str[1] != '\0' && str[1] != '/')){
return MOSQ_ERR_INVAL;
}
}else if(str[0] == '#'){
if((c != '\0' && c != '/') || str[1] != '\0'){
return MOSQ_ERR_INVAL;
}
}
#ifdef WITH_BROKER
else if(str[0] == '/'){
hier_count++;
}
#endif
len++;
c = str[0];
str = &str[1];
}
if(len > 65535) return MOSQ_ERR_INVAL;
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif
return MOSQ_ERR_SUCCESS;
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_sub_topic_check2(const char *str, size_t len)
{
char c = '\0';
size_t i;
#ifdef WITH_BROKER
int hier_count = 0;
#endif
if(str == NULL || len > 65535){
return MOSQ_ERR_INVAL;
}
for(i=0; i<len; i++){
if(str[i] == '+'){
if((c != '\0' && c != '/') || (i<len-1 && str[i+1] != '/')){
return MOSQ_ERR_INVAL;
}
}else if(str[i] == '#'){
if((c != '\0' && c != '/') || i<len-1){
return MOSQ_ERR_INVAL;
}
}
#ifdef WITH_BROKER
else if(str[i] == '/'){
hier_count++;
}
#endif
c = str[i];
}
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif
return MOSQ_ERR_SUCCESS;
}
static int topic_matches_sub(const char *sub, const char *topic, const char *clientid, const char *username, bool match_patterns, bool *result)
{
2019-03-13 16:27:13 +01:00
size_t spos;
const char *pattern_check;
const char *lastchar = NULL;
2019-03-26 13:15:34 +01:00
if(!result) return MOSQ_ERR_INVAL;
*result = false;
2019-03-13 16:27:13 +01:00
if(!sub || !topic || sub[0] == 0 || topic[0] == 0){
return MOSQ_ERR_INVAL;
}
2019-03-13 16:27:13 +01:00
if((sub[0] == '$' && topic[0] != '$')
|| (topic[0] == '$' && sub[0] != '$')){
2019-03-13 16:27:13 +01:00
return MOSQ_ERR_SUCCESS;
}
spos = 0;
2019-03-13 16:27:13 +01:00
while(sub[0] != 0){
if(topic[0] == '+' || topic[0] == '#'){
2018-10-16 23:49:13 +02:00
return MOSQ_ERR_INVAL;
}
if(match_patterns &&
(lastchar == NULL || lastchar[0] == '/') &&
sub[0] == '%' &&
(sub[1] == 'c' || sub[1] == 'u') &&
(sub[2] == '/' || sub[2] == '\0')
){
if(sub[1] == 'c'){
pattern_check = clientid;
}else{
pattern_check = username;
}
if(pattern_check == NULL || pattern_check[0] == '\0'){
return MOSQ_ERR_SUCCESS;
}
spos += 2;
sub += 2;
while(pattern_check[0] != 0 && topic[0] != 0 && topic[0] != '/'){
if(pattern_check[0] != topic[0]){
/* Valid input, but no match */
return MOSQ_ERR_SUCCESS;
}
pattern_check++;
topic++;
}
2023-11-08 15:45:07 +01:00
if(pattern_check[0] != '\0'){
/* substituted pattern part smaller than publish topic part, so fail */
return MOSQ_ERR_SUCCESS;
}
if((sub[0] == '\0' && topic[0] == '\0') ||
(sub[0] == '/' && sub[1] == '#' && sub[2] == '\0' && topic[0] == '\0')
){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
2019-03-13 16:27:13 +01:00
if(sub[0] != topic[0] || topic[0] == 0){ /* Check for wildcard matches */
if(sub[0] == '+'){
/* Check for bad "+foo" or "a/+foo" subscription */
2019-03-13 16:27:13 +01:00
if(spos > 0 && sub[-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for bad "foo+" or "foo+/a" subscription */
2019-03-13 16:27:13 +01:00
if(sub[1] != 0 && sub[1] != '/'){
return MOSQ_ERR_INVAL;
}
spos++;
2019-03-13 16:27:13 +01:00
sub++;
while(topic[0] != 0 && topic[0] != '/'){
if(topic[0] == '+' || topic[0] == '#'){
return MOSQ_ERR_INVAL;
}
2019-03-13 16:27:13 +01:00
topic++;
}
2019-03-13 16:27:13 +01:00
if(topic[0] == 0 && sub[0] == 0){
*result = true;
return MOSQ_ERR_SUCCESS;
}
2019-03-13 16:27:13 +01:00
}else if(sub[0] == '#'){
/* Check for bad "foo#" subscription */
2019-03-13 16:27:13 +01:00
if(spos > 0 && sub[-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for # not the final character of the sub, e.g. "#foo" */
2019-03-13 16:27:13 +01:00
if(sub[1] != 0){
return MOSQ_ERR_INVAL;
}else{
while(topic[0] != 0){
if(topic[0] == '+' || topic[0] == '#'){
return MOSQ_ERR_INVAL;
}
topic++;
}
*result = true;
return MOSQ_ERR_SUCCESS;
}
}else{
/* Check for e.g. foo/bar matching foo/+/# */
2019-03-13 16:27:13 +01:00
if(topic[0] == 0
&& spos > 0
&& sub[-1] == '+'
&& sub[0] == '/'
&& sub[1] == '#')
{
*result = true;
return MOSQ_ERR_SUCCESS;
}
/* There is no match at this point, but is the sub invalid? */
2019-03-13 16:27:13 +01:00
while(sub[0] != 0){
if(sub[0] == '#' && sub[1] != 0){
return MOSQ_ERR_INVAL;
}
2019-03-13 16:27:13 +01:00
spos++;
sub++;
}
/* Valid input, but no match */
return MOSQ_ERR_SUCCESS;
}
2018-10-16 23:49:13 +02:00
}else{
/* sub[spos] == topic[tpos] */
2019-03-13 16:27:13 +01:00
if(topic[1] == 0){
2018-10-16 23:49:13 +02:00
/* Check for e.g. foo matching foo/# */
2019-03-13 16:27:13 +01:00
if(sub[1] == '/'
&& sub[2] == '#'
&& sub[3] == 0){
2018-10-16 23:49:13 +02:00
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
spos++;
2019-03-13 16:27:13 +01:00
sub++;
topic++;
if(sub[0] == 0 && topic[0] == 0){
2018-10-16 23:49:13 +02:00
*result = true;
return MOSQ_ERR_SUCCESS;
2019-03-13 16:27:13 +01:00
}else if(topic[0] == 0 && sub[0] == '+' && sub[1] == 0){
if(spos > 0 && sub[-1] != '/'){
2018-10-16 23:49:13 +02:00
return MOSQ_ERR_INVAL;
}
spos++;
2019-03-13 16:27:13 +01:00
sub++;
2018-10-16 23:49:13 +02:00
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
lastchar = sub-1;
}
2019-03-13 16:27:13 +01:00
if((topic[0] != 0 || sub[0] != 0)){
*result = false;
}
while(topic[0] != 0){
if(topic[0] == '+' || topic[0] == '#'){
return MOSQ_ERR_INVAL;
}
topic++;
}
return MOSQ_ERR_SUCCESS;
}
static int sub_matches_acl(const char *acl, const char *sub, const char *clientid, const char *username, bool match_patterns, bool *result)
{
size_t apos;
const char *pattern_check;
const char *lastchar = NULL;
*result = false;
if(!acl || !sub || acl[0] == 0 || sub[0] == 0){
return MOSQ_ERR_INVAL;
}
if((acl[0] == '$' && sub[0] != '$')
|| (sub[0] == '$' && acl[0] != '$')){
return MOSQ_ERR_SUCCESS;
}
apos = 0;
while(acl[0] != 0){
if(match_patterns &&
(lastchar == NULL || lastchar[0] == '/') &&
acl[0] == '%' &&
(acl[1] == 'c' || acl[1] == 'u') &&
(acl[2] == '/' || acl[2] == '\0')
){
if(acl[1] == 'c'){
pattern_check = clientid;
}else{
pattern_check = username;
}
if(pattern_check == NULL || pattern_check[0] == '\0'){
/* no match */
return MOSQ_ERR_SUCCESS;
}
if(pattern_check[1] == '\0' && (
pattern_check[0] == '+' ||
pattern_check[0] == '#' ||
pattern_check[0] == '/')
){
/* username/client id of just + / # not allowed */
return MOSQ_ERR_SUCCESS;
}
apos +=2;
acl += 2;
while(pattern_check[0] != 0 && sub[0] != 0 && sub[0] != '/'){
if(pattern_check[0] != sub[0]){
/* Valid input, but no match */
return MOSQ_ERR_SUCCESS;
}
pattern_check++;
sub++;
}
2023-11-08 15:45:07 +01:00
if(pattern_check[0] != '\0'){
/* substituted pattern part smaller than publish topic part, so fail */
return MOSQ_ERR_SUCCESS;
}
if(sub[0] == '\0' && (acl[0] == '\0' ||
(acl[0] == '/' && acl[1] == '#' && acl[2] == '\0'))
){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
if(acl[0] != sub[0] || sub[0] == 0){ /* Check for wildcard matches */
if(acl[0] == '+'){
if(sub[0] == '#'){
/* + doesn't match # */
return MOSQ_ERR_SUCCESS;
}
/* Check for bad "+foo" or "a/+foo" subscription */
if(apos > 0 && acl[-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for bad "foo+" or "foo+/a" subscription */
if(acl[1] != 0 && acl[1] != '/'){
return MOSQ_ERR_INVAL;
}
apos++;
acl++;
while(sub[0] != 0 && sub[0] != '/'){
sub++;
}
if(sub[0] == 0 && acl[0] == 0){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}else if(acl[0] == '#'){
/* Check for bad "foo#" subscription */
if(apos > 0 && acl[-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for # not the final character of the sub, e.g. "#foo" */
if(acl[1] != 0){
return MOSQ_ERR_INVAL;
}else{
*result = true;
return MOSQ_ERR_SUCCESS;
}
}else{
/* Check for e.g. foo/bar matching foo/+/# */
if(sub[0] == 0
&& apos > 0
&& acl[-1] == '+'
&& acl[0] == '/'
&& acl[1] == '#')
{
*result = true;
return MOSQ_ERR_SUCCESS;
}
/* There is no match at this point, but is the sub invalid? */
while(acl[0] != 0){
if(acl[0] == '#' && acl[1] != 0){
return MOSQ_ERR_INVAL;
}
apos++;
acl++;
}
/* Valid input, but no match */
return MOSQ_ERR_SUCCESS;
}
}else{
/* acl[apos] == sub[spos] */
if(sub[1] == 0){
/* Check for e.g. foo matching foo/# */
if(acl[1] == '/'
&& acl[2] == '#'
&& acl[3] == 0){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
apos++;
acl++;
sub++;
if(acl[0] == 0 && sub[0] == 0){
*result = true;
return MOSQ_ERR_SUCCESS;
}else if(sub[0] == 0 && acl[0] == '+' && acl[1] == 0){
if(apos > 0 && acl[-1] != '/'){
return MOSQ_ERR_INVAL;
}
apos++;
acl++;
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
lastchar = acl-1;
}
if((sub[0] != 0 || acl[0] != 0)){
return MOSQ_ERR_SUCCESS;
}
while(sub[0] != 0){
if(sub[0] == '+' || sub[0] == '#'){
return MOSQ_ERR_SUCCESS;
}
sub++;
}
*result = true;
return MOSQ_ERR_SUCCESS;
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_sub_matches_acl(const char *acl, const char *sub, bool *result)
{
return sub_matches_acl(acl, sub, NULL, NULL, false, result);
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_sub_matches_acl_with_pattern(const char *acl, const char *sub, const char *clientid, const char *username, bool *result)
{
return sub_matches_acl(acl, sub, clientid, username, true, result);
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result)
{
return topic_matches_sub(sub, topic, NULL, NULL, false, result);
}
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_topic_matches_sub_with_pattern(const char *sub, const char *topic, const char *clientid, const char *username, bool *result)
2021-11-23 13:53:50 +01:00
{
return topic_matches_sub(sub, topic, clientid, username, true, result);
}
/* Does a topic match a subscription? */
2022-03-25 23:33:08 +01:00
BROKER_EXPORT int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *topic, size_t topiclen, bool *result)
{
size_t spos, tpos;
if(!result) return MOSQ_ERR_INVAL;
*result = false;
if(!sub || !topic || !sublen || !topiclen){
return MOSQ_ERR_INVAL;
}
if((sub[0] == '$' && topic[0] != '$')
|| (topic[0] == '$' && sub[0] != '$')){
return MOSQ_ERR_SUCCESS;
}
spos = 0;
tpos = 0;
while(spos < sublen){
if(tpos < topiclen && (topic[tpos] == '+' || topic[tpos] == '#')){
return MOSQ_ERR_INVAL;
}
if(tpos == topiclen || sub[spos] != topic[tpos]){
if(sub[spos] == '+'){
/* Check for bad "+foo" or "a/+foo" subscription */
if(spos > 0 && sub[spos-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for bad "foo+" or "foo+/a" subscription */
if(spos+1 < sublen && sub[spos+1] != '/'){
return MOSQ_ERR_INVAL;
}
spos++;
while(tpos < topiclen && topic[tpos] != '/'){
if(topic[tpos] == '+' || topic[tpos] == '#'){
return MOSQ_ERR_INVAL;
}
tpos++;
}
if(tpos == topiclen && spos == sublen){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}else if(sub[spos] == '#'){
/* Check for bad "foo#" subscription */
if(spos > 0 && sub[spos-1] != '/'){
return MOSQ_ERR_INVAL;
}
/* Check for # not the final character of the sub, e.g. "#foo" */
if(spos+1 < sublen){
return MOSQ_ERR_INVAL;
}else{
while(tpos < topiclen){
if(topic[tpos] == '+' || topic[tpos] == '#'){
return MOSQ_ERR_INVAL;
}
tpos++;
}
*result = true;
return MOSQ_ERR_SUCCESS;
}
}else{
/* Check for e.g. foo/bar matching foo/+/# */
if(tpos == topiclen
&& spos > 0
&& sub[spos-1] == '+'
&& sub[spos] == '/'
&& spos+1 < sublen
&& sub[spos+1] == '#')
{
*result = true;
return MOSQ_ERR_SUCCESS;
}
/* There is no match at this point, but is the sub invalid? */
while(spos < sublen){
if(sub[spos] == '#' && spos+1 < sublen){
return MOSQ_ERR_INVAL;
}
spos++;
}
/* Valid input, but no match */
return MOSQ_ERR_SUCCESS;
}
}else{
/* sub[spos] == topic[tpos] */
if(tpos+1 == topiclen){
/* Check for e.g. foo matching foo/# */
if(spos+3 == sublen
&& sub[spos+1] == '/'
&& sub[spos+2] == '#'){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
spos++;
tpos++;
if(spos == sublen && tpos == topiclen){
*result = true;
return MOSQ_ERR_SUCCESS;
}else if(tpos == topiclen && sub[spos] == '+' && spos+1 == sublen){
if(spos > 0 && sub[spos-1] != '/'){
return MOSQ_ERR_INVAL;
}
spos++;
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
}
if(tpos < topiclen || spos < sublen){
*result = false;
}
while(tpos < topiclen){
if(topic[tpos] == '+' || topic[tpos] == '#'){
return MOSQ_ERR_INVAL;
}
tpos++;
}
return MOSQ_ERR_SUCCESS;
}