changeset 947:95201fd9ad88

irccd: servers are now linked lists - Add reference counting to be shared with Javascript. - Implement server-disconnect command.
author David Demelier <markand@malikania.fr>
date Sat, 16 Jan 2021 09:45:33 +0100
parents 2ec05b9db2ee
children 21a91311c8ea
files MIGRATING.md irccd/main.c irccdctl/main.c lib/irccd/channel.c lib/irccd/event.h lib/irccd/irccd.c lib/irccd/irccd.h lib/irccd/js-plugin.c lib/irccd/jsapi-server.c lib/irccd/peer.c lib/irccd/server.c lib/irccd/server.h tests/test-channel.c
diffstat 13 files changed, 427 insertions(+), 168 deletions(-) [+]
line wrap: on
line diff
--- a/MIGRATING.md	Fri Jan 15 14:44:52 2021 +0100
+++ b/MIGRATING.md	Sat Jan 16 09:45:33 2021 +0100
@@ -53,6 +53,15 @@
 
 - The method `Util.ticks` as been removed.
 
+### Module Server
+
+- The property `channels` in the object returned from `Server.info` is now an
+  array of objects which also contain a list of nicknames present in the
+  channel.
+- The property `channels` in the object for the `Server` constructor now takes
+  an array of objects containing two properties each: `name` and `password`
+  which must be string (password is optional).
+
 Migrating from 2.x to 3.x
 =========================
 
--- a/irccd/main.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/irccd/main.c	Sat Jan 16 09:45:33 2021 +0100
@@ -20,8 +20,12 @@
 #include <err.h>
 
 #include <irccd/irccd.h>
+#include <irccd/js-plugin.h>
 #include <irccd/log.h>
+#include <irccd/plugin.h>
 #include <irccd/server.h>
+#include <irccd/transport.h>
+#include <irccd/util.h>
 
 int
 main(int argc, char **argv)
@@ -33,11 +37,17 @@
 		.hostname = "malikania.fr",
 		.port = 6667
 	};
-
-	irc_server_join(&s, "#test", NULL);
+	struct irc_plugin p = {
+		.name = "fuck"
+	};
 
 	irc_log_set_verbose(true);
 	irc_bot_init();
-	irc_bot_add_server(&s);
+
+	irc_transport_bind("/tmp/irccd.sock");
+	irc_server_join(&s, "#test", NULL);
+	irc_js_plugin_open(&p, "test.js");
+	irc_bot_add_server(irc_util_memdup(&s, sizeof (s)));
+	irc_bot_add_plugin(&p);
 	irc_bot_run();
 }
--- a/irccdctl/main.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/irccdctl/main.c	Sat Jan 16 09:45:33 2021 +0100
@@ -132,6 +132,17 @@
 }
 
 static void
+cmd_server_disconnect(int argc, char **argv)
+{
+	if (argc == 1)
+		req("SERVER-DISCONNECT %s", argv[0]);
+	else
+		req("SERVER-DISCONNECT");
+
+	ok();
+}
+
+static void
 cmd_server_list(int argc, char **argv)
 {
 	(void)argc;
@@ -221,6 +232,7 @@
 	void (*exec)(int, char **);
 } cmds[] = {
 	/* name                 min     max     exec                   */
+	{ "server-disconnect",  0,      1,      cmd_server_disconnect   },
 	{ "server-list",        0,      0,      cmd_server_list         },
 	{ "server-me",          3,      3,      cmd_server_me           },
 	{ "server-message",     3,      3,      cmd_server_message      },
--- a/lib/irccd/channel.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/channel.c	Sat Jan 16 09:45:33 2021 +0100
@@ -46,6 +46,9 @@
 	assert(ch);
 	assert(nick);
 
+	if (find(ch, nick))
+		return;
+
 	struct irc_channel_user u = {0};
 
 	strlcpy(u.nickname, nick, sizeof (u.nickname));
--- a/lib/irccd/event.h	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/event.h	Sat Jan 16 09:45:33 2021 +0100
@@ -50,6 +50,8 @@
 	/*
 	 * Raw arguments.
 	 *   [0]: prefix
+	 *   [1]: origin
+	 *   [2 to argsz]: arguments
 	 */
 	char args[IRC_ARGS_MAX][IRC_MESSAGE_MAX];
 	size_t argsz;
@@ -101,6 +103,10 @@
 		} nick;
 
 		struct {
+			struct irc_channel *channel;
+		} names;
+
+		struct {
 			char *origin;
 			char *channel;
 			char *message;
--- a/lib/irccd/irccd.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/irccd.c	Sat Jan 16 09:45:33 2021 +0100
@@ -89,9 +89,8 @@
 
 	for (size_t p = 0; p < irc.peersz; ++p)
 		irc_peer_prepare(&irc.peers[p], &pkg.fds[i++]);
-
-	for (size_t s = 0; s < irc.serversz; ++s)
-		irc_server_prepare(&irc.servers[s], &pkg.fds[i++]);
+	for (struct irc_server *s = irc.servers; s; s = s->next)
+		irc_server_prepare(s, &pkg.fds[i++]);
 
 	return pkg;
 }
@@ -134,8 +133,13 @@
 
 		pipe_flush(&pkg->fds[i]);
 
+#if 0
 		for (size_t s = 0; s < irc.serversz; ++s)
-			irc_server_flush(&irc.servers[s], &pkg->fds[i]);
+			irc_server_flush(irc.servers[s], &pkg->fds[i]);
+#endif
+		for (struct irc_server *s = irc.servers; s; s = s->next)
+			irc_server_flush(s, &pkg->fds[i]);
+
 
 		/* Accept new transport client. */
 		if (irc_transport_flush(&pkg->fds[i], &peer))
@@ -155,10 +159,10 @@
 	 * For every server, poll any kind of new event and pass them to the
 	 * plugin unless the rules explicitly disallow us to do so.
 	 */
-	for (size_t s = 0; s < irc.serversz; ++s) {
+	for (struct irc_server *s = irc.servers; s; s = s->next) {
 		struct irc_event ev;
 
-		while (irc_server_poll(&irc.servers[s], &ev))
+		while (irc_server_poll(s, &ev))
 			invoke(&ev);
 	}
 }
@@ -179,28 +183,34 @@
 }
 
 void
-irc_bot_add_server(const struct irc_server *s)
+irc_bot_add_server(struct irc_server *s)
 {
 	assert(s);
 
-	IRC_SET_ALLOC_PUSH(&irc.servers, &irc.serversz, s, cmp_server);
-	irc_server_connect(&irc.servers[irc.serversz - 1]);
+	irc_server_incref(s);
+	irc_server_connect(s);
+
+	s->next = irc.servers;
+	irc.servers = s;
+	irc.serversz++;
 }
 
 struct irc_server *
 irc_bot_find_server(const char *name)
 {
-	struct irc_server key = {0};
+	struct irc_server *s;
 
-	strlcpy(key.name, name, sizeof (key.name));
+	for (s = irc.servers; s; s = s->next)
+		if (strcmp(s->name, name) == 0)
+			return s;
 
-	return IRC_SET_FIND(irc.servers, irc.serversz, &key, cmp_server);
+	return NULL;
 }
 
 void
 irc_bot_remove_server(const char *name)
 {
-	struct irc_server *s;
+	struct irc_server *s, *p;
 
 	if (!(s = irc_bot_find_server(name)))
 		return;
@@ -213,8 +223,35 @@
 		.server = s
 	});
 
-	/* Finally remove from array. */
-	IRC_SET_ALLOC_REMOVE(&irc.servers, &irc.serversz, s);
+	if (s == irc.servers)
+		irc.servers = irc.servers->next;
+	else {
+		/* x -> y -> z */
+		/*      ^      */
+		/*      s      */
+		for (p = irc.servers->next; p->next != s; p = p->next)
+			continue;
+
+		p->next = s->next;
+	}
+
+	irc_server_decref(s);
+	irc.serversz--;
+}
+
+void
+irc_bot_clear_servers(void)
+{
+	struct irc_server *s, *next;
+
+	if (!(s = irc.servers))
+		return;
+
+	while (s) {
+		next = s->next;
+		irc_bot_remove_server(s->name);
+		s = next;
+	}
 }
 
 void
--- a/lib/irccd/irccd.h	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/irccd.h	Sat Jan 16 09:45:33 2021 +0100
@@ -24,13 +24,12 @@
 
 #include "rule.h"
 
+#define IRC_BOT_RULE_MAX 256
+
 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;
@@ -46,7 +45,7 @@
 irc_bot_init(void);
 
 void
-irc_bot_add_server(const struct irc_server *);
+irc_bot_add_server(struct irc_server *);
 
 struct irc_server *
 irc_bot_find_server(const char *);
@@ -55,6 +54,9 @@
 irc_bot_remove_server(const char *);
 
 void
+irc_bot_clear_servers(void);
+
+void
 irc_bot_add_plugin(const struct irc_plugin *);
 
 struct irc_plugin *
--- a/lib/irccd/js-plugin.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/js-plugin.c	Sat Jan 16 09:45:33 2021 +0100
@@ -26,6 +26,7 @@
 
 #include <duktape.h>
 
+#include "channel.h"
 #include "event.h"
 #include "js-plugin.h"
 #include "jsapi-file.h"
@@ -82,6 +83,17 @@
 	return ret ? ret : irc_util_strdup("unknown");
 }
 
+static void
+push_names(duk_context *ctx, const struct irc_channel *ch)
+{
+	duk_push_array(ctx);
+
+	for (size_t i = 0; i < ch->usersz; ++i) {
+		duk_push_string(ctx, ch->users[i].nickname);
+		duk_put_prop_index(ctx, -2, i);
+	}
+}
+
 static const char **
 get_table(duk_context *ctx, const char *name, char ***ptable)
 {
@@ -207,73 +219,52 @@
 }
 
 static void
-vcall(duk_context *ctx, const char *function, const char *fmt, va_list ap)
+vcall(struct irc_plugin *plg, const char *function, const char *fmt, va_list ap)
 {
+	struct self *self = plg->data;
 	int nargs = 0;
 
-	printf("obtain %s\n", function);
-	duk_get_global_string(ctx, function);
+	duk_get_global_string(self->ctx, function);
 
-	if (!duk_is_function(ctx, -1)) {
-		puts("not callable...");
-		duk_pop(ctx);
+	if (!duk_is_function(self->ctx, -1)) {
+		duk_pop(self->ctx);
 		return;
 	}
 
 	for (const char *f = fmt; *f; ++f) {
-		bool array = false;
 		void (*push)(duk_context *, void *);
 
 		switch (*f) {
-		case '>':
-			array = true;
-			break;
 		case 'S':
-			nargs++;
-			irc_jsapi_server_push(ctx, va_arg(ap, struct irc_server *));
+			irc_jsapi_server_push(self->ctx, va_arg(ap, struct irc_server *));
 			break;
 		case 's':
-			if (array) {
-				const char **list = va_arg(ap, const char **), **p;
-				int i = 0;
-
-				duk_push_array(ctx);
-
-				for (p = list; *p; ++p) {
-					nargs++;
-					duk_push_string(ctx, *p);
-					duk_put_prop_index(ctx, -2, i++);
-				};
-			} else {
-				nargs++;
-				duk_push_string(ctx, va_arg(ap, const char *));
-			}
+			duk_push_string(self->ctx, va_arg(ap, const char *));
 			break;
 		case 'x':
-			nargs++;
 			push = va_arg(ap, void (*)(duk_context *, void *));
-			push(ctx, va_arg(ap, void *));
+			push(self->ctx, va_arg(ap, void *));
 			break;
 		default:
-			break;
+			continue;
 		}
+
+		++nargs;
 	}
 
-	if (duk_pcall(ctx, nargs) != 0) {
-		printf("errro: %s\n", duk_to_string(ctx, -1));
-	}
+	if (duk_pcall(self->ctx, nargs) != 0)
+		irc_log_warn("plugin %s: %s\n", duk_to_string(self->ctx, -1));
 
-	duk_pop(ctx);
+	duk_pop(self->ctx);
 }
 
 static void
 call(struct irc_plugin *plg, const char *function, const char *fmt, ...)
 {
-	struct self *self = plg->data;
 	va_list ap;
 
 	va_start(ap, fmt);
-	vcall(self->ctx, function, fmt, ap);
+	vcall(plg, function, fmt, ap);
 	va_end(ap);
 }
 
@@ -311,12 +302,10 @@
 		call(plg, "onMode", "Sssssss", ev->server, ev->mode.origin, ev->mode.channel,
 		    ev->mode.mode, ev->mode.limit, ev->mode.user, ev->mode.mask);
 		break;
-#if 0
 	case IRC_EVENT_NAMES:
-		call(plg, "onNames", "Ss>s", ev->names.server, ev->names.channel,
-		    ev->names.names);
+		call(plg, "onNames", "Ssx", ev->server, ev->names.channel->name,
+		    push_names, ev->names.channel);
 		break;
-#endif
 	case IRC_EVENT_NICK:
 		call(plg, "onNick", "Sss", ev->server, ev->nick.origin, ev->nick.nickname);
 		break;
--- a/lib/irccd/jsapi-server.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/jsapi-server.c	Sat Jan 16 09:45:33 2021 +0100
@@ -22,7 +22,9 @@
 
 #include "channel.h"
 #include "irccd.h"
+#include "jsapi-server.h"
 #include "server.h"
+#include "util.h"
 
 #define SIGNATURE DUK_HIDDEN_SYMBOL("Irccd.Server")
 #define PROTOTYPE DUK_HIDDEN_SYMBOL("Irccd.Server.prototype")
@@ -30,15 +32,11 @@
 static struct irc_server *
 self(duk_context *ctx)
 {
-	/*
-	 * Server are stored using their identifiers and searched in the
-	 * registry as they may be removed at runtime.
-	 */
 	struct irc_server *sv;
 
 	duk_push_this(ctx);
 	duk_get_prop_string(ctx, -1, SIGNATURE);
-	sv = irc_bot_find_server(duk_to_string(ctx, -1));
+	sv = duk_to_pointer(ctx, -1);
 	duk_pop_2(ctx);
 
 	if (!sv)
@@ -56,7 +54,7 @@
 		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
 
 	duk_get_prop_string(ctx, index, SIGNATURE);
-	sv = irc_bot_find_server(duk_to_string(ctx, -1));
+	sv = duk_to_pointer(ctx, -1);
 	duk_pop(ctx);
 
 	return sv;
@@ -87,9 +85,28 @@
 
 	duk_push_array(ctx);
 
-	for (size_t i = 0; i < s->channelsz; ++i) {
-		duk_push_string(ctx, s->channels[i].name);
-		duk_put_prop_index(ctx, -2, i);
+	for (size_t c = 0; c < s->channelsz; ++c) {
+		duk_push_object(ctx);
+		duk_push_string(ctx, s->channels[c].name);
+		duk_put_prop_string(ctx, -2, "name");
+		duk_push_boolean(ctx, s->channels[c].joined);
+		duk_put_prop_string(ctx, -2, "joined");
+		duk_push_array(ctx);
+
+		for (size_t n = 0; n < s->channels[c].usersz; ++n) {
+			duk_push_object(ctx);
+			duk_push_string(ctx, s->channels[c].users[n].nickname);
+			duk_put_prop_string(ctx, -2, "nickname");
+			if (s->channels[c].users[n].mode)
+				duk_push_sprintf(ctx, "%c", s->channels[c].users[n].mode);
+			else
+				duk_push_null(ctx);
+			duk_put_prop_string(ctx, -2, "mode");
+			duk_put_prop_index(ctx, -2, n);
+		}
+
+		duk_put_prop_string(ctx, -2, "users");
+		duk_put_prop_index(ctx, -2, c);
 	}
 
 	duk_put_prop_string(ctx, -2, "channels");
@@ -232,9 +249,7 @@
 		throw server_error(server_error::invalid_channel);
 #endif
 
-#if 0
-	duk_push_boolean(ctx, irc_server_names(channel));
-#endif
+	duk_push_boolean(ctx, irc_server_names(s, channel));
 
 	return 1;
 }
@@ -250,11 +265,9 @@
 		throw server_error(server_error::invalid_nickname);
 #endif
 
-#if 0
-	duk_push_boolean(set_nickname(std::move(nickname));
-#endif
+	duk_push_boolean(ctx, irc_server_nick(s, nickname));
 
-	return 0;
+	return 1;
 }
 
 static duk_ret_t
@@ -350,94 +363,176 @@
 	return 1;
 }
 
+static inline void
+get_name(duk_context *ctx, struct irc_server *s)
+{
+	duk_get_prop_string(ctx, 0, "name");
+
+	if (!duk_is_string(ctx, -1))
+		duk_error(ctx, DUK_ERR_ERROR, "invalid 'name' property");
+
+	strlcpy(s->name, duk_to_string(ctx, -1), sizeof (s->name));
+	duk_pop(ctx);
+}
+
+static inline void
+get_port(duk_context *ctx, struct irc_server *s)
+{
+	duk_get_prop_string(ctx, 0, "port");
+
+	if (!duk_is_number(ctx, -1))
+		duk_error(ctx, DUK_ERR_ERROR, "invalid 'port' property");
+
+	s->port = duk_to_int(ctx, -1);
+	duk_pop(ctx);
+}
+
+static inline void
+get_ip(duk_context *ctx, struct irc_server *s)
+{
+	enum irc_server_flags flags = IRC_SERVER_FLAGS_IPV4 |
+	                              IRC_SERVER_FLAGS_IPV6;
+
+	duk_get_prop_string(ctx, 0, "ipv4");
+	duk_get_prop_string(ctx, 0, "ipv6");
+
+	if (duk_is_boolean(ctx, -1) && !duk_to_boolean(ctx, -1))
+		flags &= ~(IRC_SERVER_FLAGS_IPV4);
+	if (duk_is_boolean(ctx, -2) && !duk_to_boolean(ctx, -2))
+		flags &= ~(IRC_SERVER_FLAGS_IPV6);
+
+	s->flags |= flags;
+	duk_pop_n(ctx, 2);
+}
+
+static inline void
+get_ssl(duk_context *ctx, struct irc_server *s)
+{
+	duk_get_prop_string(ctx, 0, "ssl");
+
+	if (duk_is_boolean(ctx, -1) && duk_to_boolean(ctx, -1))
+		s->flags |= IRC_SERVER_FLAGS_SSL;
+
+	duk_pop(ctx);
+}
+
+static inline void
+get_string(duk_context *ctx, const char *n, char *dst, size_t dstsz)
+{
+	duk_get_prop_string(ctx, 0, n);
+
+	if (duk_is_string(ctx, -1) && duk_is_string(ctx ,-1))
+		strlcpy(dst, duk_to_string(ctx, -1), dstsz);
+
+	duk_pop(ctx);
+}
+
+static inline void
+get_channels(duk_context *ctx, struct irc_server *s)
+{
+	duk_get_prop_string(ctx, 0, "channels");
+
+	for (duk_enum(ctx, -1, 0); duk_next(ctx, -1, true); ) {
+		duk_get_prop_string(ctx, -1, "name");
+		duk_get_prop_string(ctx, -2, "password");
+
+		if (!duk_is_string(ctx, -2))
+			duk_error(ctx, DUK_ERR_ERROR, "invalid channel 'name' property");
+
+		irc_server_join(s, duk_to_string(ctx, -2), duk_opt_string(ctx, -1, NULL));
+		duk_pop_n(ctx, 4);
+	}
+
+	duk_pop_n(ctx, 2);
+}
+
 static duk_ret_t
 Server_constructor(duk_context *ctx)
 {
-#if 0
-	return wrap(ctx, [] (auto ctx) {
-		if (!duk_is_constructor_call(ctx))
-			return 0;
+	struct irc_server s = {0}, *p;
 
-		duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+	duk_require_object(ctx, 0);
 
-		auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
-		auto s = from_json(duk::type_traits<bot>::self(ctx).get_service(), json);
+	get_name(ctx, &s);
+	get_port(ctx, &s);
+	get_ip(ctx, &s);
+	get_ssl(ctx, &s);
+	get_string(ctx, "nickname", s.nickname, sizeof (s.nickname));
+	get_string(ctx, "username", s.username, sizeof (s.username));
+	get_string(ctx, "realname", s.realname, sizeof (s.realname));
+	get_string(ctx, "commandChar", s.commandchar, sizeof (s.commandchar));
+	get_channels(ctx, &s);
 
-		duk_push_this(ctx);
-		duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
-		duk_put_prop_string(ctx, -2, signature.data());
-		duk_pop(ctx);
+	p = irc_util_memdup(&s, sizeof (s));
+	irc_server_incref(p);
 
-		return 0;
-	});
-#endif
+	duk_push_this(ctx);
+	duk_push_pointer(ctx, p);
+	duk_put_prop_string(ctx, -2, SIGNATURE);
+	duk_pop(ctx);
+
 	return 0;
 }
 
-#if 0
+static duk_ret_t
+Server_destructor(duk_context *ctx)
+{
+	struct irc_server *sv;
+
+	duk_get_prop_string(ctx, 0, SIGNATURE);
+
+	if ((sv = duk_to_pointer(ctx, -1)))
+		irc_server_decref(sv);
+
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, SIGNATURE);
+
+	return 0;
+}
 
 static duk_ret_t
 Server_add(duk_context *ctx)
 {
-	return wrap(ctx, [] (auto ctx) {
-		duk::type_traits<bot>::self(ctx).get_servers().add(
-			duk::require<std::shared_ptr<server>>(ctx, 0));
+	struct irc_server *sv = require(ctx, 0);
 
-		return 0;
-	});
+	return 0;
 }
 
-#endif
-
-#if 0
-
 static duk_ret_t
 Server_find(duk_context *ctx)
 {
-	return wrap(ctx, [] (auto ctx) {
-		auto id = duk::require<std::string>(ctx, 0);
-		auto server = duk::type_traits<bot>::self(ctx).get_servers().get(id);
-
-		if (!server)
-			return 0;
+	const char *name = duk_require_string(ctx, 0);
+	struct irc_server *s = irc_bot_find_server(name);
 
-		duk::push(ctx, server);
+	if (!s)
+		return 0;
 
-		return 1;
-	});
+	irc_jsapi_server_push(ctx, s);
+
+	return 1;
 }
 
-#endif
-
-#if 0
-
 static duk_ret_t
 Server_list(duk_context *ctx)
 {
 	duk_push_object(ctx);
 
-	for (const auto& server : duk::type_traits<bot>::self(ctx).get_servers().list()) {
-		duk::push(ctx, server);
-		duk_put_prop_string(ctx, -2, server->get_id().c_str());
+	for (struct irc_server *s = irc.servers; s; s = s->next) {
+		irc_jsapi_server_push(ctx, s);
+		duk_put_prop_string(ctx, -2, s->name);
 	}
 
 	return 1;
 }
 
-#endif
-
-#if 0
-
 static duk_ret_t
 Server_remove(duk_context *ctx)
 {
-	duk::type_traits<bot>::self(ctx).get_servers().remove(duk_require_string(ctx, 0));
+	irc_bot_remove_server(duk_require_string(ctx, 0));
 
 	return 0;
 }
 
-#endif
-
 static const duk_function_list_entry methods[] = {
 	{ "info",       Server_prototype_info,          0               },
 	{ "invite",     Server_prototype_invite,        2               },
@@ -459,12 +554,10 @@
 };
 
 static const duk_function_list_entry functions[] = {
-#if 0
 	{ "add",        Server_add,                     1               },
 	{ "find",       Server_find,                    1               },
 	{ "list",       Server_list,                    0               },
 	{ "remove",     Server_remove,                  1               },
-#endif
 	{ NULL,         NULL,                           0               }
 };
 
@@ -492,6 +585,8 @@
 	assert(ctx);
 	assert(s);
 
+	irc_server_incref(s);
+
 	duk_push_object(ctx);
 	duk_push_string(ctx, s->name);
 	duk_put_prop_string(ctx, -2, SIGNATURE);
--- a/lib/irccd/peer.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/peer.c	Sat Jan 16 09:45:33 2021 +0100
@@ -84,6 +84,27 @@
 }
 
 /*
+ * DISCONNECT [server]
+ */
+static int
+cmd_server_disconnect(struct irc_peer *p, char *line)
+{
+	const char *args[1] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 1) == 1) {
+		if (!(s = require_server(p, args[0])))
+			return 0;
+
+		irc_server_disconnect(s);
+	} else
+		for (struct irc_server *s = irc.servers; s; s = s->next)
+			irc_server_disconnect(s);
+
+	return ok(p);
+}
+
+/*
  * MESSAGE server channel message
  */
 static int
@@ -225,10 +246,10 @@
 {
 	char out[IRC_BUF_MAX] = "OK ";
 
-	for (size_t i = 0; i < irc.serversz; ++i) {
-		if (strlcat(out, irc.servers[i].name, sizeof (out)) >= sizeof (out))
+	for (struct irc_server *s = irc.servers; s; s = s->next) {
+		if (strlcat(out, s->name, sizeof (out)) >= sizeof (out))
 			return EMSGSIZE;
-		if (i + 1 < irc.serversz && strlcat(out, " ", sizeof (out)) >= sizeof (out))
+		if (s->next && strlcat(out, " ", sizeof (out)) >= sizeof (out))
 			return EMSGSIZE;
 	}
 
--- a/lib/irccd/server.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/server.c	Sat Jan 16 09:45:33 2021 +0100
@@ -96,6 +96,22 @@
 	return &origin;
 }
 
+static void
+add_nick(const struct irc_server *s, struct irc_channel *ch, const char *nick)
+{
+	char mode = 0;
+
+	for (size_t i = 0; i < IRC_UTIL_SIZE(s->prefixes); ++i) {
+		if (nick[0] == s->prefixes[i].token) {
+			mode = s->prefixes[i].mode;
+			++nick;
+			break;
+		}
+	}
+
+	irc_channel_add(ch, nick, mode);
+}
+
 static struct irc_channel *
 add_channel(struct irc_server *s, const char *name, const char *password, bool joined)
 {
@@ -176,25 +192,26 @@
 convert_join(struct irc_server *s, struct irc_event *ev)
 {
 	const struct origin *origin = parse_origin(ev->args[0]);
-	struct irc_channel *ch;
+	struct irc_channel *ch = NULL;
 
 	ev->type = IRC_EVENT_JOIN;
 	ev->server = s;
 	ev->join.origin = ev->args[0];
 	ev->join.channel = ev->args[2];
 
-	/* Also add a channel if the bot joined. */
-	if (strcmp(s->nickname, origin->nickname)) {
-		if ((ch = irc_server_find(s, ev->args[2])))
-			ch->joined = true;
-		else
-			add_channel(s, ev->args[2], NULL, true);
-	}
+	if (!(ch = irc_server_find(s, ev->args[2])))
+		ch = add_channel(s, ev->args[2], NULL, true);
+	else
+		ch->joined = true;
+
+	irc_channel_add(ch, origin->nickname, 0);
 }
 
 static void
 convert_kick(struct irc_server *s, struct irc_event *ev)
 {
+	struct irc_channel *ch = irc_server_find(s, ev->args[2]);
+
 	ev->type = IRC_EVENT_KICK;
 	ev->server = s;
 	ev->kick.origin = ev->args[0];
@@ -206,16 +223,14 @@
 	 * If the bot was kicked itself mark the channel as not joined and
 	 * rejoin it automatically if the option is set.
 	 */
-	if (strcmp(ev->args[3], s->nickname) == 0) {
-		struct irc_channel *ch = irc_server_find(s, ev->args[2]);
+	if (strcmp(ev->args[3], s->nickname) == 0 && ch) {
+		ch->joined = false;
+		irc_channel_clear(ch);
 
-		if (ch) {
-			ch->joined = false;
-
-			if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN)
-				irc_server_join(s, ch->name, ch->password);
-		}
-	}
+		if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN)
+			irc_server_join(s, ch->name, ch->password);
+	} else
+		irc_channel_remove(ch, ev->args[3]);
 }
 
 static void
@@ -305,27 +320,35 @@
 static void
 convert_names(struct irc_server *s, struct irc_event *ev)
 {
-	(void)s;
-	(void)ev;
-#if 0
-	struct irc_channel *chan;
+	struct irc_channel *ch;
 	char *p, *n;
 
-	if (m->argsz < 3 || !(chan = irc_server_find(s, m->args[2])))
+	if (ev->argsz < 6)
 		return;
 
-	/*
-	 * Message arguments are as following:
-	 * 0------- 1 2------- 3--------------------
-	 * yourself = #channel nick1 nick2 nick3 ...
-	 */
-	for (p = m->args[3]; p; p = n ? n + 1 : NULL) {
-		if ((n = strpbrk(p, " ")))
-			*n = 0;
+	/* TODO: create if not exist */
+	ch = irc_server_find(s, ev->args[4]);
+
+	for (p = ev->args[5]; p; ) {
+		n = strchr(p, ' ');
+
+		if (n)
+			*n = '\0';
+		if (strlen(p) > 0)
+			add_nick(s, ch, p);
 
-		channel_add(chan, s, p);
+		p = n ? n + 1 : NULL;
 	}
-#endif
+}
+
+static void
+convert_endofnames(struct irc_server *s, struct irc_event *ev)
+{
+	for (size_t i = 0; i < ev->argsz; ++i)
+		printf("%zd: %s\n", i, ev->args[i]);
+	ev->type = IRC_EVENT_NAMES;
+	ev->server = s;
+	ev->names.channel = irc_server_find(s, ev->args[3]);
 }
 
 static const struct convert {
@@ -336,6 +359,7 @@
 	{ "001",        convert_connect          },
 	{ "005",        convert_support          },
 	{ "353",        convert_names            },
+	{ "366",        convert_endofnames       },
 	{ "JOIN",       convert_join             },
 	{ "KICK",       convert_kick             },
 	{ "MODE",       convert_mode             },
@@ -976,6 +1000,26 @@
 }
 
 bool
+irc_server_names(struct irc_server *s, const char *channel)
+{
+	return irc_server_send(s, "NAMES %s", channel);
+}
+
+bool
+irc_server_nick(struct irc_server *s, const char *nick)
+{
+	assert(s);
+	assert(nick);
+
+	if (s->state == IRC_SERVER_STATE_DISCONNECTED) {
+		strlcpy(s->nickname, nick, sizeof (s->nickname));
+		return true;
+	}
+
+	return irc_server_send(s, "NICK %s", nick);
+}
+
+bool
 irc_server_notice(struct irc_server *s, const char *channel, const char *message)
 {
 	assert(s);
@@ -986,11 +1030,22 @@
 }
 
 void
-irc_server_finish(struct irc_server *s)
+irc_server_incref(struct irc_server *s)
 {
 	assert(s);
 
-	clear(s);
-	free(s->channels);
-	memset(s, 0, sizeof (*s));
+	s->refc++;
 }
+
+void
+irc_server_decref(struct irc_server *s)
+{
+	assert(s);
+	assert(s->refc >= 1);
+
+	if (--s->refc == 0) {
+		clear(s);
+		free(s->channels);
+		free(s);
+	}
+}
--- a/lib/irccd/server.h	Fri Jan 15 14:44:52 2021 +0100
+++ b/lib/irccd/server.h	Sat Jan 16 09:45:33 2021 +0100
@@ -44,7 +44,9 @@
 
 enum irc_server_flags {
 	IRC_SERVER_FLAGS_SSL           = (1 << 0),
-	IRC_SERVER_FLAGS_AUTO_REJOIN   = (1 << 1)
+	IRC_SERVER_FLAGS_AUTO_REJOIN   = (1 << 1),
+	IRC_SERVER_FLAGS_IPV4          = (1 << 2),
+	IRC_SERVER_FLAGS_IPV6          = (1 << 3)
 };
 
 struct irc_server_prefix {
@@ -97,6 +99,10 @@
 	enum irc_server_ssl_state ssl_state;
 #endif
 
+	/* Reference count. */
+	size_t refc;
+	struct irc_server *next;
+
 	/* IRC server settings. */
 	char chantypes[8];
 	struct irc_server_prefix prefixes[16];
@@ -153,9 +159,18 @@
                 const char *);
 
 bool
+irc_server_names(struct irc_server *, const char *);
+
+bool
+irc_server_nick(struct irc_server *, const char *);
+
+bool
 irc_server_notice(struct irc_server *, const char *, const char *);
 
 void
-irc_server_finish(struct irc_server *);
+irc_server_incref(struct irc_server *);
+
+void
+irc_server_decref(struct irc_server *);
 
 #endif /* !IRCCD_SERVER_H */
--- a/tests/test-channel.c	Fri Jan 15 14:44:52 2021 +0100
+++ b/tests/test-channel.c	Sat Jan 16 09:45:33 2021 +0100
@@ -31,6 +31,11 @@
 	GREATEST_ASSERT_EQ(ch.users[0].mode, '@');
 	GREATEST_ASSERT_STR_EQ(ch.users[0].nickname, "markand");
 
+	irc_channel_add(&ch, "markand", '@');
+	GREATEST_ASSERT_EQ(ch.usersz, 1U);
+	GREATEST_ASSERT_EQ(ch.users[0].mode, '@');
+	GREATEST_ASSERT_STR_EQ(ch.users[0].nickname, "markand");
+
 	irc_channel_add(&ch, "jean", 0);
 	GREATEST_ASSERT_EQ(ch.usersz, 2U);
 	GREATEST_ASSERT_EQ(ch.users[0].mode, 0);