changeset 944:d63a360811dd

irccd: add rules
author David Demelier <markand@malikania.fr>
date Fri, 15 Jan 2021 10:38:45 +0100
parents aef1568a76bf
children 8ddeceeee0f2
files .hgignore Makefile irccd/main.c lib/irccd/irccd.c lib/irccd/irccd.h lib/irccd/rule.c lib/irccd/rule.h tests/test-rule.c
diffstat 8 files changed, 630 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Jan 14 18:56:00 2021 +0100
+++ b/.hgignore	Fri Jan 15 10:38:45 2021 +0100
@@ -32,6 +32,7 @@
 # tests.
 ^tests/test-dl-plugin$
 ^tests/test-log$
+^tests/test-rule$
 ^tests/test-subst$
 ^tests/test-util$
 
--- a/Makefile	Thu Jan 14 18:56:00 2021 +0100
+++ b/Makefile	Fri Jan 15 10:38:45 2021 +0100
@@ -47,6 +47,7 @@
 LIBIRCCD_SRCS+=         lib/irccd/log.c
 LIBIRCCD_SRCS+=         lib/irccd/peer.c
 LIBIRCCD_SRCS+=         lib/irccd/plugin.c
+LIBIRCCD_SRCS+=         lib/irccd/rule.c
 LIBIRCCD_SRCS+=         lib/irccd/server.c
 LIBIRCCD_SRCS+=         lib/irccd/subst.c
 LIBIRCCD_SRCS+=         lib/irccd/transport.c
@@ -71,8 +72,9 @@
 
 TESTS=                  tests/test-dl-plugin.c
 TESTS+=                 tests/test-log.c
+TESTS+=                 tests/test-rule.c
+TESTS+=                 tests/test-subst.c
 TESTS+=                 tests/test-util.c
-TESTS+=                 tests/test-subst.c
 TESTS_OBJS=             ${TESTS:.c=}
 
 DEFINES=                -D_BSD_SOURCE
@@ -133,7 +135,7 @@
 
 ${LIBIRCCD_OBJS}: ${LIBCOMPAT} lib/irccd/config.h
 
-${LIBIRCCD}: ${LIBIRCCD_OBJS}
+${LIBIRCCD}: ${LIBIRCCD_OBJS} ${LIBDUKTAPE}
 	${CMD.ar}
 
 ${IRCCD}: ${IRCCD_OBJS} ${LIBCOMPAT} ${LIBDUKTAPE} ${LIBIRCCD}
@@ -143,10 +145,8 @@
 	${CMD.ccld}
 
 # Unit tests.
-tests/test-%.o: tests/test-%.c
-	${CMD.cc}
-tests/test-%: tests/test-%.o ${LIBCOMPAT} ${IRCCD_OBJS}
-	${CMD.ccld}
+tests/test-%: tests/test-%.c
+	${CC} ${DEFINES} ${INCS} ${CFLAGS} -o $@ $< ${LIBS} ${LDFLAGS}
 
 ${TESTS_OBJS}: ${LIBIRCCD}
 
--- a/irccd/main.c	Thu Jan 14 18:56:00 2021 +0100
+++ b/irccd/main.c	Fri Jan 15 10:38:45 2021 +0100
@@ -27,40 +27,49 @@
 #include <irccd/server.h>
 #include <irccd/transport.h>
 
+#include <irccd/rule.h>
+
 static struct irc_plugin js = {
 	.name = "example"
 };
 
+#include <string.h>
+
+static void
+dump(void)
+{
+	for (size_t i = 0; i < irc.rulesz; ++i) {
+		printf("== rule %zd ==\n", i);
+		printf("servers => %s\n", irc.rules[i].servers);
+	}
+}
+
 int
 main(int argc, char **argv)
 {
-	struct irc_server s = {
-		.name = "malikania",
-		.hostname = "malikania.fr",
-		.port = 6667,
-		.nickname = "circ",
-		.username = "circ",
-		.realname = "circ"
-	};
-	struct irc_server freenode = {
-		.name = "freenode",
-		.hostname = "chat.freenode.net",
-		.port = 6667,
-		.nickname = "circ",
-		.username = "circ",
-		.realname = "circ"
-	};
+	struct irc_rule r = {0};
+
+	irc_rule_add(r.servers, "malikania");
+	//irc_rule_add(r.servers, "freenode");
+
+	printf("%d\n", irc_rule_match(&r, "malikania", "", "", "", ""));
 
-	irc_init();
-	irc_log_set_verbose(true);
-	irc_server_join(&s, "#test", NULL);
-	irc_transport_bind("/tmp/irccd.sock");
-	irc_add_server(&s);
-	irc_add_server(&freenode);
 #if 0
-	if (!irc_js_plugin_open(&js, "/Users/markand/Dev/irccd-4/test.js"))
-		return 1;
-	irc_add_plugin(&js);
+	irc_rule_add(r.servers, "malikania");
+	irc_bot_insert_rule(&r, 0);
+	strcpy(r.servers, "freenode:");
+	irc_bot_insert_rule(&r, 15);
+	strcpy(r.servers, "oftc:");
+	irc_bot_insert_rule(&r, 0);
+	strcpy(r.servers, "jean:");
+	irc_bot_insert_rule(&r, 1);
+
+	puts("BEFORE");
+	dump();
+	irc_bot_remove_rule(3);
+	puts("AFTER");
+	dump();
 #endif
-	irc_run();
+
+
 }
--- a/lib/irccd/irccd.c	Thu Jan 14 18:56:00 2021 +0100
+++ b/lib/irccd/irccd.c	Fri Jan 15 10:38:45 2021 +0100
@@ -18,6 +18,7 @@
 
 #include <assert.h>
 #include <err.h>
+#include <errno.h>
 #include <poll.h>
 #include <stdlib.h>
 #include <string.h>
@@ -30,11 +31,9 @@
 #include "server.h"
 #include "peer.h"
 #include "transport.h"
+#include "rule.h"
 #include "util.h"
 
-/* TODO: TMP */
-#include <stdio.h>
-
 #define APPEND(a, l, o, f)                                              \
 do {                                                                    \
         a = irc_util_reallocarray(a, ++l, sizeof (*o));                 \
@@ -285,6 +284,36 @@
 	REMOVE(irc.plugins, irc.pluginsz, cmp_plugin);
 }
 
+bool
+irc_bot_insert_rule(const struct irc_rule *rule, size_t i)
+{
+	assert(rule);
+
+	if (irc.rulesz >= IRC_RULE_MAX) {
+		errno = ENOMEM;
+		return false;
+	}
+
+	if (i >= irc.rulesz)
+		i = irc.rulesz;
+
+	memmove(&irc.rules[i + 1], &irc.rules[i], sizeof (*irc.rules) * (irc.rulesz++ - i));
+	memcpy(&irc.rules[i], rule, sizeof (*rule));
+
+	return true;
+}
+
+void
+irc_bot_remove_rule(size_t i)
+{
+	assert(i < irc.rulesz);
+
+	if (i + 1 >= irc.rulesz)
+		irc.rulesz--;
+	else
+		memmove(&irc.rules[i], &irc.rules[i + 1], sizeof (*irc.rules) * (irc.rulesz-- - i));
+}
+
 void
 irc_post(void (*exec)(void *), void *data)
 {
--- a/lib/irccd/irccd.h	Thu Jan 14 18:56:00 2021 +0100
+++ b/lib/irccd/irccd.h	Fri Jan 15 10:38:45 2021 +0100
@@ -19,12 +19,18 @@
 #ifndef IRCCD_H
 #define IRCCD_H
 
+#include <stdbool.h>
 #include <stddef.h>
 
+#include "rule.h"
+
 struct irc_server;
 struct irc_plugin;
 struct irc_peer;
 
+
+#define IRC_BOT_RULE_MAX 256
+
 extern struct irc {
 	struct irc_peer *peers;
 	size_t peersz;
@@ -32,6 +38,8 @@
 	size_t pluginsz;
 	struct irc_server *servers;
 	size_t serversz;
+	struct irc_rule rules[IRC_BOT_RULE_MAX];
+	size_t rulesz;
 } irc;
 
 void
@@ -55,6 +63,12 @@
 void
 irc_del_plugin(const char *);
 
+bool
+irc_bot_insert_rule(const struct irc_rule *, size_t);
+
+void
+irc_bot_remove_rule(size_t);
+
 void
 irc_post(void (*)(void *), void *);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/rule.c	Fri Jan 15 10:38:45 2021 +0100
@@ -0,0 +1,154 @@
+/*
+ * rule.c -- rule filtering
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include "rule.h"
+#include "util.h"
+
+static void
+lower(char *dst, const char *src)
+{
+	while (*src)
+		*dst++ = tolower(*src++);
+}
+
+static inline char *
+find(const char *str, const char *value)
+{
+	char strlower[IRC_RULE_MAX] = {0};
+	char valuelower[IRC_RULE_MAX] = {0};
+	char *p;
+
+	lower(strlower, str);
+	lower(valuelower, value);
+
+	if ((p = strstr(strlower, valuelower)))
+		return (char *)&str[p - strlower];
+
+	return NULL;
+}
+
+static inline bool
+match(const char *str, const char *value)
+{
+	size_t len = strlen(value);
+	const char *p;
+
+	if (!str[0])
+		return true;
+	if (len == 0 || !(p = find(str, value)))
+		return false;
+
+	/*
+	 * Consider the following scenario:
+	 *
+	 * value = no
+	 * str   = mlk:freenode:
+	 * p     =         ^
+	 */
+	while (p != str && *p != ':')
+		--p;
+	if (*p == ':')
+		++p;
+
+	return strncasecmp(p, value, len) == 0;
+}
+
+bool
+irc_rule_add(char *str, const char *value)
+{
+	size_t slen, vlen;
+
+	if (find(str, value))
+		return true;
+
+	slen = strlen(str);
+	vlen = strlen(value);
+
+	if (vlen + 1 >= IRC_RULE_MAX - slen) {
+		errno = ENOMEM;
+		return false;
+	}
+
+	sprintf(&str[slen], "%s:", value);
+
+	return true;
+}
+
+void
+irc_rule_remove(char *str, const char *value)
+{
+	char *pos;
+	size_t vlen, slen;
+
+	if (!(pos = find(str, value)))
+		return;
+
+	slen = strlen(str);
+	vlen = strlen(value) + 1;       /* includes ':' */
+
+	assert(pos[vlen - 1] == ':');
+	memmove(&pos[0], &pos[vlen], IRC_RULE_MAX - (&pos[vlen] - str));
+}
+
+bool
+irc_rule_match(const struct irc_rule *rule,
+               const char *server,
+               const char *channel,
+               const char *origin,
+               const char *plugin,
+               const char *event)
+{
+	return match(rule->servers, server)     &&
+	       match(rule->channels, channel)   &&
+	       match(rule->origins, origin)     &&
+	       match(rule->plugins, plugin)     &&
+	       match(rule->events, event);
+}
+
+bool
+irc_rule_matchlist(const struct irc_rule *rules,
+                   size_t rulesz,
+                   const char *server,
+                   const char *channel,
+                   const char *origin,
+                   const char *plugin,
+                   const char *event)
+{
+	bool result = true;
+
+	for (size_t i = 0; i < rulesz; ++i)
+		if (irc_rule_match(&rules[i], server, channel, origin, plugin, event))
+			result = rules[i].action == IRC_RULE_ACCEPT;
+
+	return result;
+}
+
+void
+irc_rule_finish(struct irc_rule *rule)
+{
+	assert(rule);
+
+	memset(rule, 0, sizeof (*rule));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/rule.h	Fri Jan 15 10:38:45 2021 +0100
@@ -0,0 +1,67 @@
+/*
+ * rule.h -- rule filtering
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_RULE_H
+#define IRCCD_RULE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#define IRC_RULE_MAX 1024
+
+enum irc_rule_action {
+	IRC_RULE_ACCEPT,
+	IRC_RULE_DROP
+};
+
+struct irc_rule {
+	enum irc_rule_action action;
+	char servers[IRC_RULE_MAX];
+	char channels[IRC_RULE_MAX];
+	char origins[IRC_RULE_MAX];
+	char plugins[IRC_RULE_MAX];
+	char events[IRC_RULE_MAX];
+};
+
+bool
+irc_rule_add(char *, const char *);
+
+void
+irc_rule_remove(char *, const char *);
+
+bool
+irc_rule_match(const struct irc_rule *,
+               const char *,
+               const char *,
+               const char *,
+               const char *,
+               const char *);
+
+bool
+irc_rule_matchlist(const struct irc_rule *,
+                   size_t,
+                   const char *,
+                   const char *,
+                   const char *,
+                   const char *,
+                   const char *);
+
+void
+irc_rule_finish(struct irc_rule *);
+
+#endif /* !IRCCD_RULE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rule.c	Fri Jan 15 10:38:45 2021 +0100
@@ -0,0 +1,321 @@
+/*
+ * test-rule.c -- test rule.h functions
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define GREATEST_USE_ABBREVS 0
+#include <greatest.h>
+
+#include <irccd/rule.h>
+#include <irccd/irccd.h>
+
+static void
+clean(void *udata)
+{
+	(void)udata;
+
+	memset(&irc, 0, sizeof (irc));
+}
+
+GREATEST_TEST
+basics_insert(void)
+{
+	struct irc_rule r1 = {0}, r2 = {0};
+
+	irc_rule_add(r1.servers, "s1");
+	irc_rule_add(r2.servers, "s2");
+
+	irc_bot_insert_rule(&r1, 0);
+	irc_bot_insert_rule(&r2, 0);
+
+	GREATEST_ASSERT_EQ(2U, irc.rulesz);
+	GREATEST_ASSERT(memcmp(&irc.rules[0], &r2, sizeof (r2)) == 0);
+	GREATEST_ASSERT(memcmp(&irc.rules[1], &r1, sizeof (r1)) == 0);
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+basics_remove(void)
+{
+	struct irc_rule r1 = {0}, r2 = {0}, r3 = {0};
+
+	irc_rule_add(r1.servers, "s1");
+	irc_rule_add(r2.servers, "s2");
+	irc_rule_add(r3.servers, "s3");
+
+	irc_bot_insert_rule(&r1, -1);
+	irc_bot_insert_rule(&r2, -1);
+	irc_bot_insert_rule(&r3, -1);
+	irc_bot_remove_rule(1);
+
+	GREATEST_ASSERT_EQ(2U, irc.rulesz);
+	GREATEST_ASSERT(memcmp(&irc.rules[0], &r1, sizeof (r1)) == 0);
+	GREATEST_ASSERT(memcmp(&irc.rules[1], &r3, sizeof (r3)) == 0);
+
+	irc_bot_remove_rule(1);
+	GREATEST_ASSERT_EQ(1U, irc.rulesz);
+	GREATEST_ASSERT(memcmp(&irc.rules[0], &r1, sizeof (r1)) == 0);
+
+	irc_bot_remove_rule(0);
+	GREATEST_ASSERT_EQ(0U, irc.rulesz);
+
+	GREATEST_PASS();
+}
+
+/*
+ * Simulate the following rules configuration:
+ *
+ * #
+ * # On all servers, each channel #staff can't use the onCommand event,
+ * # everything else is allowed.
+ * #
+ * [rule]       #1
+ * servers      = ""
+ * channels     = "#staff"
+ * events       = "onCommand"
+ * action       = drop
+ *
+ * #
+ * # However, the same onCommand on #staff is allowed on server "unsafe"
+ * #
+ * [rule]       #2
+ * servers      = "unsafe"
+ * channels     = "#staff"
+ * events       = "onCommand"
+ * action       = accept
+ *
+ * #
+ * # Plugin game is only allowed on server "malikania" and "localhost",
+ * # channel "#games" and events "onMessage, onCommand".
+ * #
+ * # The first rule #3-1 disable the plugin game for every server, it is
+ * # reenabled again with the #3-2.
+ * #
+ * [rule]       #3-1
+ * plugins      = "game"
+ * action       = drop
+ *
+ * [rule]       #3-2
+ * servers      = "malikania localhost"
+ * channels     = "#games"
+ * plugins      = "game"
+ * events       = "onMessage onCommand"
+ * action       = accept
+ */
+
+static void
+set(void *udata)
+{
+	(void)udata;
+
+	struct irc_rule r1 = {0}, r2 = {0}, r31 = {0}, r32 = {0};
+
+	/* Deinit irccd first. */
+	memset(&irc, 0, sizeof (irc));
+
+	/* #1 */
+	r1.action = IRC_RULE_DROP;
+	irc_rule_add(r1.channels, "#staff");
+	irc_rule_add(r1.events, "onCommand");
+	irc_bot_insert_rule(&r1, -1);
+
+	/* #2 */
+	r2.action = IRC_RULE_ACCEPT;
+	irc_rule_add(r2.servers, "unsafe");
+	irc_rule_add(r2.channels, "#staff");
+	irc_rule_add(r2.events, "onCommand");
+	irc_bot_insert_rule(&r2, -1);
+
+	/* #3-1 */
+	r31.action = IRC_RULE_DROP;
+	irc_rule_add(r31.plugins, "game");
+	irc_bot_insert_rule(&r31, -1);
+
+	/* #3-2 */
+	r32.action = IRC_RULE_ACCEPT;
+	irc_rule_add(r32.servers, "malikania");
+	irc_rule_add(r32.servers, "localhost");
+	irc_rule_add(r32.channels, "#games");
+	irc_rule_add(r32.plugins, "game");
+	irc_rule_add(r32.events, "onCommand");
+	irc_rule_add(r32.events, "onMessage");
+	irc_bot_insert_rule(&r32, -1);
+};
+
+GREATEST_TEST
+solve_match1(void)
+{
+	struct irc_rule m = {0};
+
+	GREATEST_ASSERT(irc_rule_match(&m, "freenode", "#test", "a", "", ""));
+	GREATEST_ASSERT(irc_rule_match(&m, "", "", "", "", ""));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match2(void)
+{
+	struct irc_rule m = {0};
+
+	irc_rule_add(m.servers, "freenode");
+
+	GREATEST_ASSERT(irc_rule_match(&m, "FreeNode", "#test", "a", "", ""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#test", "a", "", ""));
+	GREATEST_ASSERT(irc_rule_match(&m, "freenode", "", "jean", "", "onMessage"));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match3(void)
+{
+	struct irc_rule m = {0};
+
+	irc_rule_add(m.servers, "freenode");
+	irc_rule_add(m.channels, "#staff");
+
+	GREATEST_ASSERT(irc_rule_match(&m, "freenode", "#staff", "a", "", ""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "freenode", "#test", "a", "", ""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "a", "", ""));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match4(void)
+{
+	struct irc_rule m = {0};
+
+	irc_rule_add(m.servers, "malikania");
+	irc_rule_add(m.channels, "#staff");
+	irc_rule_add(m.origins, "a");
+
+	GREATEST_ASSERT(irc_rule_match(&m, "malikania", "#staff", "a", "",""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "b", "", ""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "freenode", "#staff", "a", "", ""));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match5(void)
+{
+	struct irc_rule m = {0};
+
+	irc_rule_add(m.servers, "malikania");
+	irc_rule_add(m.servers, "freenode");
+
+	GREATEST_ASSERT(irc_rule_match(&m, "malikania", "", "", "", ""));
+	GREATEST_ASSERT(irc_rule_match(&m, "freenode", "", "", "", ""));
+	GREATEST_ASSERT(!irc_rule_match(&m, "no", "", "", "", ""));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match6(void)
+{
+	struct irc_rule m = {0};
+
+	irc_rule_add(m.servers, "malikania");
+	irc_rule_add(m.origins, "markand");
+
+	GREATEST_ASSERT(irc_rule_match(&m, "malikania", "#staff", "markand", "system", "onCommand"));
+	GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "", "system", "onNames"));
+	GREATEST_ASSERT(!irc_rule_match(&m, "malikania", "#staff", "jean", "system", "onMessage"));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match7(void)
+{
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#staff", "", "a", "onMessage"));
+
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#staff", "", "b", "onTopic"));
+
+	/* Not allowed */
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#staff", "", "", "onCommand"));
+
+	/* Not allowed */
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#staff", "", "c", "onCommand"));
+
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "unsafe", "#staff", "", "c", "onCommand"));
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match8(void)
+{
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onMessage"));
+
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "localhost", "#games", "", "game", "onMessage"));
+
+	/* Allowed */
+	GREATEST_ASSERT(irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onCommand"));
+
+	/* Not allowed */
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#games", "", "game", "onQuery"));
+
+	/* Not allowed */
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "freenode", "#no", "", "game", "onMessage"));
+
+	/* Not allowed */
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "malikania", "#test", "", "game", "onMessage"));
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+solve_match9(void)
+{
+	GREATEST_ASSERT(!irc_rule_matchlist(irc.rules, irc.rulesz, "MALIKANIA", "#STAFF", "", "SYSTEM", "onCommand"));
+	GREATEST_PASS();
+}
+
+GREATEST_SUITE(suite_basics)
+{
+	GREATEST_SET_SETUP_CB(clean, NULL);
+	GREATEST_RUN_TEST(basics_insert);
+	GREATEST_RUN_TEST(basics_remove);
+}
+
+GREATEST_SUITE(suite_solve)
+{
+	GREATEST_SET_SETUP_CB(set, NULL);
+	GREATEST_RUN_TEST(solve_match1);
+	GREATEST_RUN_TEST(solve_match2);
+	GREATEST_RUN_TEST(solve_match3);
+	GREATEST_RUN_TEST(solve_match4);
+	GREATEST_RUN_TEST(solve_match5);
+	GREATEST_RUN_TEST(solve_match6);
+	GREATEST_RUN_TEST(solve_match7);
+	GREATEST_RUN_TEST(solve_match8);
+	GREATEST_RUN_TEST(solve_match9);
+}
+
+GREATEST_MAIN_DEFS();
+
+int
+main(int argc, char **argv)
+{
+	GREATEST_MAIN_BEGIN();
+	GREATEST_RUN_SUITE(suite_basics);
+	GREATEST_RUN_SUITE(suite_solve);
+	GREATEST_MAIN_END();
+
+	return 0;
+}