Add mosquitto_topic_matches_sub_with_pattern()

And use it in the default security checks.
pull/2229/head
Roger A. Light 4 years ago
parent c9c5889b31
commit 6502d6e5f4

@ -35,6 +35,9 @@ Client library:
- Callbacks no longer block other callbacks, and can be set from within a
callback. Closes #2127.
- Add support for MQTT v5 broker to client topic aliases.
- Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against
subscriptions with `%c` and `%u` patterns for client id / username
substitution.
Clients:
- Add `-o` option for all clients loading options from a specific file.

@ -2445,35 +2445,55 @@ libmosq_EXPORT int mosquitto_sub_topic_tokens_free(char ***topics, int count);
* Returns:
* MOSQ_ERR_SUCCESS - on success
* MOSQ_ERR_INVAL - if the input parameters were invalid.
* MOSQ_ERR_NOMEM - if an out of memory condition occurred.
*/
libmosq_EXPORT int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result);
/*
* Function: mosquitto_topic_matches_sub2
*
* Check whether a topic matches a subscription.
* Identical to <mosquitto_topic_matches_sub>. The sublen and topiclen
* parameters are *IGNORED*.
*/
libmosq_EXPORT int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *topic, size_t topiclen, bool *result);
/*
* Function: mosquitto_topic_matches_sub_with_pattern
*
* Check whether a topic matches a subscription, with client id/username
* pattern substitution.
*
* Any instances of a topic hierarchy that are exactly %c or %u will be
* replaced with the client id or username respectively.
*
* For example:
*
* foo/bar would match the subscription foo/# or +/bar
* non/matching would not match the subscription non/+/+
* mosquitto_topic_matches_sub_with_pattern("sensors/%c/temperature", "sensors/kitchen/temperature", "kitchen", NULL, &result)
* -> this will match
*
* mosquitto_topic_matches_sub_with_pattern("sensors/%c/temperature", "sensors/bathroom/temperature", "kitchen", NULL, &result)
* -> this will not match
*
* mosquitto_topic_matches_sub_with_pattern("sensors/%count/temperature", "sensors/kitchen/temperature", "kitchen", NULL, &result)
* -> this will not match - the `%count` is not treated as a pattern
*
* mosquitto_topic_matches_sub_with_pattern("%c/%c/%u/%u", "kitchen/kitchen/bathroom/bathroom", "kitchen", "bathroom", &result)
* -> this will match
*
* Parameters:
* sub - subscription string to check topic against.
* sublen - length in bytes of sub string
* topic - topic to check.
* topiclen - length in bytes of topic string
* clientid - client id to substitute in patterns. If NULL, then any %c patterns will not match.
* username - username to substitute in patterns. If NULL, then any %u patterns will not match.
* result - bool pointer to hold result. Will be set to true if the topic
* matches the subscription.
*
* Returns:
* MOSQ_ERR_SUCCESS - on success
* MOSQ_ERR_INVAL - if the input parameters were invalid.
* MOSQ_ERR_NOMEM - if an out of memory condition occurred.
*/
libmosq_EXPORT int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *topic, size_t topiclen, bool *result);
libmosq_EXPORT int mosquitto_topic_matches_sub_with_pattern(const char *sub, const char *topic, const char *clientid, const char *username, bool *result);
/*
* Function: mosquitto_pub_topic_check

@ -145,4 +145,5 @@ MOSQ_1.7 {
MOSQ_2.1 {
global:
mosquitto_pre_connect_callback_set;
mosquitto_topic_matches_sub_with_pattern;
} MOSQ_1.7;

@ -189,18 +189,10 @@ int mosquitto_sub_topic_check2(const char *str, size_t len)
return MOSQ_ERR_SUCCESS;
}
int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result)
{
return mosquitto_topic_matches_sub2(sub, 0, topic, 0, result);
}
/* Does a topic match a subscription? */
int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *topic, size_t topiclen, bool *result)
static int topic_matches_sub(const char *sub, const char *topic, const char *clientid, const char *username, bool match_patterns, bool *result)
{
size_t spos;
UNUSED(sublen);
UNUSED(topiclen);
const char *pattern_check;
if(!result) return MOSQ_ERR_INVAL;
*result = false;
@ -221,6 +213,39 @@ int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *top
if(topic[0] == '+' || topic[0] == '#'){
return MOSQ_ERR_INVAL;
}
if(match_patterns &&
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++;
}
if((sub[0] == '\0' && topic[0] == '\0') ||
(sub[0] == '/' && sub[1] == '#' && sub[2] == '\0' && topic[0] == '\0')
){
*result = true;
return MOSQ_ERR_SUCCESS;
}
}
if(sub[0] != topic[0] || topic[0] == 0){ /* Check for wildcard matches */
if(sub[0] == '+'){
/* Check for bad "+foo" or "a/+foo" subscription */
@ -325,3 +350,24 @@ int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *top
return MOSQ_ERR_SUCCESS;
}
int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result)
{
return topic_matches_sub(sub, topic, NULL, NULL, false, result);
}
/* Does a topic match a subscription? */
int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *topic, size_t topiclen, bool *result)
{
UNUSED(sublen);
UNUSED(topiclen);
return topic_matches_sub(sub, topic, NULL, NULL, false, result);
}
int mosquitto_topic_matches_sub_with_pattern(const char *sub, const char *topic, const char *clientid, const char *username, bool *result)
{
return topic_matches_sub(sub, topic, clientid, username, true, result);
}

@ -368,12 +368,8 @@ static int add__acl_pattern(struct mosquitto__security_options *security_opts, c
static int mosquitto_acl_check_default(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_acl_check *ed = event_data;
char *local_acl;
struct mosquitto__acl *acl_root;
bool result;
size_t i;
size_t len, tlen, clen, ulen;
char *s;
struct mosquitto__security_options *security_opts = NULL;
UNUSED(event);
@ -446,47 +442,14 @@ static int mosquitto_acl_check_default(int event, void *event_data, void *userda
/* Loop through all pattern ACLs. ACL denial patterns are iterated over first. */
if(!ed->client->id) return MOSQ_ERR_ACL_DENIED;
clen = strlen(ed->client->id);
while(acl_root){
tlen = strlen(acl_root->topic);
if(acl_root->ucount && !ed->client->username){
acl_root = acl_root->next;
continue;
}
if(ed->client->username){
ulen = strlen(ed->client->username);
len = tlen + (size_t)acl_root->ccount*(clen-2) + (size_t)acl_root->ucount*(ulen-2);
}else{
ulen = 0;
len = tlen + (size_t)acl_root->ccount*(clen-2);
}
local_acl = mosquitto__malloc(len+1);
if(!local_acl) return MOSQ_ERR_NOMEM;
s = local_acl;
for(i=0; i<tlen; i++){
if(i<tlen-1 && acl_root->topic[i] == '%'){
if(acl_root->topic[i+1] == 'c'){
i++;
strncpy(s, ed->client->id, clen);
s+=clen;
continue;
}else if(ed->client->username && acl_root->topic[i+1] == 'u'){
i++;
strncpy(s, ed->client->username, ulen);
s+=ulen;
continue;
}
}
s[0] = acl_root->topic[i];
s++;
}
local_acl[len] = '\0';
mosquitto_topic_matches_sub(local_acl, ed->topic, &result);
mosquitto__free(local_acl);
mosquitto_topic_matches_sub_with_pattern(acl_root->topic, ed->topic, ed->client->id, ed->client->username, &result);
if(result){
if(acl_root->access == MOSQ_ACL_NONE){
/* Access was explicitly denied for this topic pattern. */

@ -27,44 +27,60 @@ def write_acl(filename, global_en, user_en, pattern_en):
def single_test(port, per_listener, username, topic, expect_deny):
rc = 1
keepalive = 60
connect_packet = mosq_test.gen_connect("acl-check", keepalive=keepalive, username=username)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 1
subscribe_packet = mosq_test.gen_subscribe(mid=mid, topic=topic, qos=1)
suback_packet = mosq_test.gen_suback(mid=mid, qos=1)
mid = 2
publish1s_packet = mosq_test.gen_publish(topic=topic, mid=mid, qos=1, payload="message")
puback1s_packet = mosq_test.gen_puback(mid)
mid=1
publish1r_packet = mosq_test.gen_publish(topic=topic, mid=mid, qos=1, payload="message")
sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
sock.send(publish1s_packet)
if expect_deny:
mosq_test.expect_packet(sock, "puback", puback1s_packet)
mosq_test.do_ping(sock)
else:
mosq_test.receive_unordered(sock, puback1s_packet, publish1r_packet, "puback / publish1r")
sock.close()
def acl_test(port, per_listener, global_en, user_en, pattern_en):
acl_file = os.path.basename(__file__).replace('.py', '.acl')
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_acl(acl_file, global_en=global_en, user_en=user_en, pattern_en=pattern_en)
write_config(conf_file, port, per_listener)
broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port)
rc = 0
try:
keepalive = 60
connect_packet = mosq_test.gen_connect("acl-check", keepalive=keepalive, username=username)
connack_packet = mosq_test.gen_connack(rc=0)
mid = 1
subscribe_packet = mosq_test.gen_subscribe(mid=mid, topic=topic, qos=1)
suback_packet = mosq_test.gen_suback(mid=mid, qos=1)
mid = 2
publish1s_packet = mosq_test.gen_publish(topic=topic, mid=mid, qos=1, payload="message")
puback1s_packet = mosq_test.gen_puback(mid)
mid=1
publish1r_packet = mosq_test.gen_publish(topic=topic, mid=mid, qos=1, payload="message")
sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port)
mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback")
sock.send(publish1s_packet)
if expect_deny:
mosq_test.expect_packet(sock, "puback", puback1s_packet)
mosq_test.do_ping(sock)
else:
mosq_test.receive_unordered(sock, puback1s_packet, publish1r_packet, "puback / publish1r")
sock.close()
rc = 0
if global_en:
single_test(port, per_listener, username=None, topic="topic/global", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/global", expect_deny=True)
single_test(port, per_listener, username=None, topic="topic/global/except", expect_deny=True)
if user_en:
single_test(port, per_listener, username=None, topic="topic/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="topic/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/username/except", expect_deny=True)
if pattern_en:
single_test(port, per_listener, username=None, topic="pattern/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="pattern/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="pattern/username/except", expect_deny=True)
except mosq_test.TestError:
pass
rc = 1
finally:
os.remove(conf_file)
os.remove(acl_file)
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
@ -72,35 +88,13 @@ def single_test(port, per_listener, username, topic, expect_deny):
print(stde.decode('utf-8'))
exit(rc)
def acl_test(port, per_listener, global_en, user_en, pattern_en):
acl_file = os.path.basename(__file__).replace('.py', '.acl')
write_acl(acl_file, global_en=global_en, user_en=user_en, pattern_en=pattern_en)
if global_en:
single_test(port, per_listener, username=None, topic="topic/global", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/global", expect_deny=True)
single_test(port, per_listener, username=None, topic="topic/global/except", expect_deny=True)
if user_en:
single_test(port, per_listener, username=None, topic="topic/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="topic/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/username/except", expect_deny=True)
if pattern_en:
single_test(port, per_listener, username=None, topic="pattern/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="pattern/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="pattern/username/except", expect_deny=True)
def do_test(port, per_listener):
try:
acl_test(port, per_listener, global_en=False, user_en=False, pattern_en=True)
acl_test(port, per_listener, global_en=False, user_en=True, pattern_en=False)
acl_test(port, per_listener, global_en=True, user_en=False, pattern_en=False)
acl_test(port, per_listener, global_en=False, user_en=True, pattern_en=True)
acl_test(port, per_listener, global_en=True, user_en=False, pattern_en=True)
acl_test(port, per_listener, global_en=True, user_en=True, pattern_en=True)
finally:
acl_file = os.path.basename(__file__).replace('.py', '.acl')
os.remove(acl_file)
acl_test(port, per_listener, global_en=False, user_en=False, pattern_en=True)
acl_test(port, per_listener, global_en=False, user_en=True, pattern_en=False)
acl_test(port, per_listener, global_en=True, user_en=False, pattern_en=False)
acl_test(port, per_listener, global_en=False, user_en=True, pattern_en=True)
acl_test(port, per_listener, global_en=True, user_en=False, pattern_en=True)
acl_test(port, per_listener, global_en=True, user_en=True, pattern_en=True)
port = mosq_test.get_port()

@ -84,6 +84,72 @@ static void TEST_empty_input(void)
CU_ASSERT_EQUAL(match, false);
}
static void TEST_pattern_empty_input(void)
{
int rc;
bool match;
rc = mosquitto_topic_matches_sub_with_pattern(NULL, NULL, NULL, NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("sub", NULL, NULL, NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern(NULL, "topic", NULL, NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern(NULL, NULL, "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern(NULL, NULL, NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("sub", "", "", "", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("", "topic", "", "", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("", "", "clientid", "", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("", "", "", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%c", "topic", NULL, NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%u", "topic", NULL, NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%c", "", "", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%u", "", NULL, "", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_INVAL);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/test", "test//test", "", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/test", "test//test", NULL, "", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
}
/* ========================================================================
* VALID MATCHING AND NON-MATCHING
* ======================================================================== */
@ -181,6 +247,254 @@ static void TEST_invalid(void)
no_match_helper(MOSQ_ERR_INVAL, "/#a", "foo/bar");
}
/* ========================================================================
* PATTERNS
* ======================================================================== */
static void TEST_pattern_clientid(void)
{
int rc;
bool match;
/* Sole pattern */
rc = mosquitto_topic_matches_sub_with_pattern("%c", "clientid", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("%c", "clientid", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern at beginning */
rc = mosquitto_topic_matches_sub_with_pattern("%c/test", "clientid/test", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("%c/test", "clientid/test", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern at end */
rc = mosquitto_topic_matches_sub_with_pattern("test/%c", "test/clientid", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c", "test/clientid", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern in middle */
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/test", "test/clientid/test", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/test", "test/clientid/test", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Repeated pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%c/test", "test/clientid/clientid/test", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%c/test", "test/clientid/clientid/test", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Not a pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%count", "test/clientid", "clientid", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
}
static void TEST_pattern_username(void)
{
int rc;
bool match;
/* Sole pattern */
rc = mosquitto_topic_matches_sub_with_pattern("%u", "username", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("%u", "username", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern at beginning */
rc = mosquitto_topic_matches_sub_with_pattern("%u/test", "username/test", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("%u/test", "username/test", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern at end */
rc = mosquitto_topic_matches_sub_with_pattern("test/%u", "test/username", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%u", "test/username", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern in middle */
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/test", "test/username/test", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/test", "test/username/test", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Repeated pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/%u/test", "test/username/username/test", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/%u/test", "test/username/username/test", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Not a pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%username", "test/username", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
}
static void TEST_pattern_both(void)
{
int rc;
bool match;
/* Sole pattern */
rc = mosquitto_topic_matches_sub_with_pattern("%u/%c", "username/clientid", "clientid", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("%u/%c", "username/clientid", "clientid", "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%u/%c", "username/clientid", "nomatch", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("%u/%c", "username/clientid", "nomatch", "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Pattern in middle */
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%u/test", "test/clientid/username/test", "clientid", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%u/test", "test/clientid/username/test", "clientid", "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%u/test", "test/clientid/username/test", "nomatch", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("test/%c/%u/test", "test/clientid/username/test", "nomatch", "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Repeated pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%u/%c/%c/%u/test", "test/username/clientid/clientid/username/test", "clientid", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
/* Not a pattern */
rc = mosquitto_topic_matches_sub_with_pattern("test/%username/%client", "test/username/clientid", "clientid", "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
}
static void TEST_pattern_wildcard(void)
{
int rc;
bool match;
/* Malicious */
/* ========= */
/* / in client id */
rc = mosquitto_topic_matches_sub_with_pattern("%c", "clientid/test", "clientid/test", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* / in username */
rc = mosquitto_topic_matches_sub_with_pattern("%u", "username/test", NULL, "username/test", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* + in client id */
rc = mosquitto_topic_matches_sub_with_pattern("%c", "clientid", "+", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* + in username */
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/+", "username/test/+", NULL, "+", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Valid */
/* ========= */
/* Ends in + */
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/+", "clientid/test/topic", "test", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/+", "clientid/test/topic", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/+", "username/test/topic", NULL, "test", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/+", "username/test/topic", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
/* Ends in # */
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/#", "clientid/test/topic", "test", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/#", "clientid/test/topic", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/#", "username/test/topic", NULL, "test", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/#", "username/test/topic", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/#", "clientid/test", "test", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("clientid/%c/#", "clientid/test", "nomatch", NULL, &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
rc = mosquitto_topic_matches_sub_with_pattern("pattern/%u/#", "pattern/username", NULL, "username", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, true);
rc = mosquitto_topic_matches_sub_with_pattern("username/%u/#", "username/test", NULL, "nomatch", &match);
CU_ASSERT_EQUAL(rc, MOSQ_ERR_SUCCESS);
CU_ASSERT_EQUAL(match, false);
}
/* ========================================================================
* PUB TOPIC CHECK
* ======================================================================== */
@ -287,6 +601,11 @@ int init_util_topic_tests(void)
|| !CU_add_test(test_suite, "Pub topic: Invalid", TEST_pub_topic_invalid)
|| !CU_add_test(test_suite, "Sub topic: Valid", TEST_sub_topic_valid)
|| !CU_add_test(test_suite, "Sub topic: Invalid", TEST_sub_topic_invalid)
|| !CU_add_test(test_suite, "Pattern topic: Empty input", TEST_pattern_empty_input)
|| !CU_add_test(test_suite, "Pattern topic: clientid", TEST_pattern_clientid)
|| !CU_add_test(test_suite, "Pattern topic: username", TEST_pattern_username)
|| !CU_add_test(test_suite, "Pattern topic: both", TEST_pattern_both)
|| !CU_add_test(test_suite, "Pattern topic: wildcard", TEST_pattern_wildcard)
){
printf("Error adding util topic CUnit tests.\n");

Loading…
Cancel
Save