changeset 957:3167c51f0c84

irccd: rework events
author David Demelier <markand@malikania.fr>
date Thu, 21 Jan 2021 15:34:25 +0100
parents 5e682f1cebcc
children 533639ec5e9c
files lib/irccd/event.c lib/irccd/event.h lib/irccd/irccd.c lib/irccd/js-plugin.c lib/irccd/server.c lib/irccd/server.h lib/irccd/util.h
diffstat 7 files changed, 596 insertions(+), 331 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/event.c	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/event.c	Thu Jan 21 15:34:25 2021 +0100
@@ -24,86 +24,6 @@
 #include "event.h"
 #include "server.h"
 
-static inline void
-scan(char **line, char **str)
-{
-	char *p = strchr(*line, ' ');
-
-	if (p)
-		*p = '\0';
-
-	*str = *line;
-	*line = p ? p + 1 : strchr(*line, '\0');
-}
-
-bool
-irc_event_is_ctcp(const char *line)
-{
-	size_t length;
-
-	if (!line)
-		return false;
-	if ((length = strlen(line)) < 2)
-		return false;
-
-	return line[0] == 0x1 && line[length - 1] == 0x1;
-}
-
-char *
-irc_event_ctcp(char *line)
-{
-	/* Skip first \001. */
-	if (*line == '\001')
-		line++;
-
-	/* Remove last \001. */
-	line[strcspn(line, "\001")] = '\0';
-
-	if (strncmp(line, "ACTION ", 7) == 0)
-		line += 7;
-
-	return line;
-}
-
-bool
-irc_event_parse(struct irc_event_msg *ev, const char *line)
-{
-	assert(ev);
-	assert(line);
-
-	char *ptr = ev->buf;
-	size_t a;
-
-	memset(ev, 0, sizeof (*ev));
-	strlcpy(ev->buf, line, sizeof (ev->buf));
-
-	/*
-	 * IRC message is defined as following:
-	 *
-	 * [:prefix] command arg1 arg2 [:last-argument]
-	 */
-	if (*line == ':')
-		scan((++ptr, &ptr), &ev->prefix);     /* prefix */
-
-	scan(&ptr, &ev->cmd);                         /* command */
-
-	/* And finally arguments. */
-	for (a = 0; *ptr && a < IRC_ARGS_MAX; ++a) {
-		if (*ptr == ':') {
-			ev->args[a] = ptr + 1;
-			ptr = strchr(ptr, '\0');
-		} else
-			scan(&ptr, &ev->args[a]);
-	}
-
-	if (a >= IRC_ARGS_MAX)
-		return errno = EMSGSIZE, false;
-	if (ev->cmd == NULL)
-		return errno = EBADMSG, false;
-
-	return true;
-}
-
 bool
 irc_event_str(const struct irc_event *ev, char *str, size_t strsz)
 {
@@ -114,82 +34,144 @@
 
 	switch (ev->type) {
 	case IRC_EVENT_CONNECT:
-		written = snprintf(str, strsz, "EVENT-CONNECT %s", ev->server->name);
+		written = snprintf(str, strsz, "EVENT-CONNECT %s",
+		    ev->server->name);
 		break;
 	case IRC_EVENT_DISCONNECT:
-		written = snprintf(str, strsz, "EVENT-DISCONNECT %s", ev->server->name);
+		written = snprintf(str, strsz, "EVENT-DISCONNECT %s",
+		    ev->server->name);
 		break;
 	case IRC_EVENT_INVITE:
-		written = snprintf(str, strsz, "EVENT-INVITE %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]);
+		written = snprintf(str, strsz, "EVENT-INVITE %s %s %s %s",
+		    ev->server->name, ev->invite.origin, ev->invite.channel,
+		    ev->invite.target);
 		break;
 	case IRC_EVENT_JOIN:
-		written = snprintf(str, strsz, "EVENT-JOIN %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0]);
+		written = snprintf(str, strsz, "EVENT-JOIN %s %s %s",
+		    ev->server->name, ev->join.origin, ev->join.channel);
 		break;
 	case IRC_EVENT_KICK:
-		written = snprintf(str, strsz, "EVENT-KICK %s %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1],
-		    ev->msg.args[2] ? ev->msg.args[2] : "");
+		written = snprintf(str, strsz, "EVENT-KICK %s %s %s %s %s",
+		    ev->server->name, ev->kick.origin, ev->kick.channel,
+		    ev->kick.target, ev->kick.reason ? ev->kick.reason : "");
 		break;
 	case IRC_EVENT_ME:
-		written = snprintf(str, strsz, "EVENT-ME %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]);
+		written = snprintf(str, strsz, "EVENT-ME %s %s %s %s",
+		    ev->server->name, ev->message.origin, ev->message.channel,
+		    ev->message.message);
 		break;
 	case IRC_EVENT_MESSAGE:
-		written = snprintf(str, strsz, "EVENT-MESSAGE %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]);
+		written = snprintf(str, strsz, "EVENT-MESSAGE %s %s %s %s",
+		    ev->server->name, ev->message.origin, ev->message.channel,
+		    ev->message.message);
 		break;
 	case IRC_EVENT_MODE:
 		written = snprintf(str, strsz, "EVENT-MODE %s %s %s %s %s %s %s",
-		    ev->server->name, ev->msg.prefix, ev->msg.args[0],
-		    ev->msg.args[1] ? ev->msg.args[1] : "",
-		    ev->msg.args[2] ? ev->msg.args[2] : "",
-		    ev->msg.args[3] ? ev->msg.args[3] : "",
-		    ev->msg.args[4] ? ev->msg.args[4] : "");
+		    ev->server->name, ev->mode.origin, ev->mode.channel,
+		    ev->mode.mode,
+		    ev->mode.limit ? ev->mode.limit : "",
+		    ev->mode.user  ? ev->mode.user  : "",
+		    ev->mode.mask  ? ev->mode.mask  : "");
 		break;
 	case IRC_EVENT_NICK:
-		written = snprintf(str, strsz, "EVENT-NICK %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0]);
+		written = snprintf(str, strsz, "EVENT-NICK %s %s %s",
+		    ev->server->name, ev->nick.origin, ev->nick.nickname);
 		break;
 	case IRC_EVENT_NOTICE:
-		written = snprintf(str, strsz, "EVENT-NOTICE %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]);
+		written = snprintf(str, strsz, "EVENT-NOTICE %s %s %s %s",
+		    ev->server->name, ev->notice.origin, ev->notice.channel,
+		    ev->notice.notice);
 		break;
 	case IRC_EVENT_PART:
-		written = snprintf(str, strsz, "EVENT-PART %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0],
-		    ev->msg.args[1] ? ev->msg.args[1] : "");
+		written = snprintf(str, strsz, "EVENT-PART %s %s %s %s",
+		    ev->server->name, ev->part.origin, ev->part.channel,
+		    ev->part.reason ? ev->part.reason : "");
 		break;
 	case IRC_EVENT_TOPIC:
-		written = snprintf(str, strsz, "EVENT-TOPIC %s %s %s %s", ev->server->name,
-		    ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]);
+		written = snprintf(str, strsz, "EVENT-TOPIC %s %s %s %s",
+		    ev->server->name, ev->topic.origin, ev->topic.channel,
+		    ev->topic.topic);
 		break;
 	case IRC_EVENT_WHOIS:
-		snprintf(str, strsz, "EVENT-WHOIS %s %s %s %s %s ",
-		    ev->server->name, ev->whois->nickname, ev->whois->username,
-		    ev->whois->realname, ev->whois->hostname);
-
-		for (size_t i = 0; i < ev->whois->channelsz; ++i) {
-			size_t s;
-
-			/* Concat channel and their modes. */
-			strlcat(str, (char []) {ev->whois->channels[i].mode, '\0' }, strsz);
-			strlcat(str, ev->whois->channels[i].channel, strsz);
-
-			if ((s = strlcat(str, " ", strsz)) >= strsz)
-				goto emsgsize;
-
-			written = s;
-		}
+		snprintf(str, strsz, "EVENT-WHOIS %s %s %s %s %s %s",
+		    ev->server->name, ev->whois.nickname, ev->whois.username,
+		    ev->whois.realname, ev->whois.hostname, ev->whois.channels);
 		break;
 	default:
 		break;
 	}
 
 	return written > 0;
+}
 
-emsgsize:
-	errno = EMSGSIZE;
-	return false;
+void
+irc_event_finish(struct irc_event *ev)
+{
+	assert(ev);
+
+	switch (ev->type) {
+	case IRC_EVENT_INVITE:
+		free(ev->invite.origin);
+		free(ev->invite.channel);
+		break;
+	case IRC_EVENT_JOIN:
+		free(ev->join.origin);
+		free(ev->join.channel);
+		break;
+	case IRC_EVENT_KICK:
+		free(ev->kick.origin);
+		free(ev->kick.channel);
+		free(ev->kick.target);
+		free(ev->kick.reason);
+		break;
+	case IRC_EVENT_COMMAND:
+	case IRC_EVENT_ME:
+	case IRC_EVENT_MESSAGE:
+		free(ev->message.origin);
+		free(ev->message.channel);
+		free(ev->message.message);
+		break;
+	case IRC_EVENT_MODE:
+		free(ev->mode.origin);
+		free(ev->mode.channel);
+		free(ev->mode.mode);
+		free(ev->mode.limit);
+		free(ev->mode.user);
+		free(ev->mode.mask);
+		break;
+	case IRC_EVENT_NAMES:
+		free(ev->names.channel);
+		free(ev->names.names);
+		break;
+	case IRC_EVENT_NICK:
+		free(ev->nick.origin);
+		free(ev->nick.nickname);
+		break;
+	case IRC_EVENT_NOTICE:
+		free(ev->notice.origin);
+		free(ev->notice.channel);
+		free(ev->notice.notice);
+		break;
+	case IRC_EVENT_PART:
+		free(ev->part.origin);
+		free(ev->part.channel);
+		free(ev->part.reason);
+		break;
+	case IRC_EVENT_TOPIC:
+		free(ev->topic.origin);
+		free(ev->topic.channel);
+		free(ev->topic.topic);
+		break;
+	case IRC_EVENT_WHOIS:
+		free(ev->whois.nickname);
+		free(ev->whois.username);
+		free(ev->whois.realname);
+		free(ev->whois.hostname);
+		free(ev->whois.channels);
+		break;
+	default:
+		break;
+	}
+
+	memset(ev, 0, sizeof (*ev));
 }
--- a/lib/irccd/event.h	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/event.h	Thu Jan 21 15:34:25 2021 +0100
@@ -26,51 +26,126 @@
 
 struct irc_server;
 
+/*
+ * This enumeration indicates which kind of event (not mandatory IRC related)
+ * happened on a server. It is used in conjunction with underlying sub
+ * structures of type irc_event_* that is store into the generic irc_event
+ * structure.
+ *
+ * Some events may contain specific data that is accessible via the anonymous
+ * union inside the struct irc_event, refer to the enumerator description to
+ * check which member must be accessed in that case.
+ */
 enum irc_event_type {
-	IRC_EVENT_UNKNOWN,
-	IRC_EVENT_COMMAND,
-	IRC_EVENT_CONNECT,
-	IRC_EVENT_DISCONNECT,
-	IRC_EVENT_INVITE,
-	IRC_EVENT_JOIN,
-	IRC_EVENT_KICK,
-	IRC_EVENT_ME,
-	IRC_EVENT_MESSAGE,
-	IRC_EVENT_MODE,
-	IRC_EVENT_NAMES,
-	IRC_EVENT_NICK,
-	IRC_EVENT_NOTICE,
-	IRC_EVENT_PART,
-	IRC_EVENT_TOPIC,
-	IRC_EVENT_WHOIS
+	IRC_EVENT_UNKNOWN,      /* Unknown or private use.      */
+	IRC_EVENT_COMMAND,      /* Use irc_event_message.       */
+	IRC_EVENT_CONNECT,      /* No specific data.            */
+	IRC_EVENT_DISCONNECT,   /* No specific data.            */
+	IRC_EVENT_INVITE,       /* Use irc_event_invite.        */
+	IRC_EVENT_JOIN,         /* Use irc_event_join.          */
+	IRC_EVENT_KICK,         /* Use irc_event_kick.          */
+	IRC_EVENT_ME,           /* Use irc_event_message        */
+	IRC_EVENT_MESSAGE,      /* Use irc_event_message.       */
+	IRC_EVENT_MODE,         /* Use irc_event_mode.          */
+	IRC_EVENT_NAMES,        /* Use irc_event_names.         */
+	IRC_EVENT_NICK,         /* Use irc_event_nick.          */
+	IRC_EVENT_NOTICE,       /* Use irc_event_notice.        */
+	IRC_EVENT_PART,         /* Use irc_event_part.          */
+	IRC_EVENT_TOPIC,        /* Use irc_event_topic.         */
+	IRC_EVENT_WHOIS         /* Use irc_event_whois.         */
+};
+
+struct irc_event_invite {
+	char *origin;
+	char *channel;
+	char *target;
+};
+
+struct irc_event_join {
+	char *origin;
+	char *channel;
+};
+
+struct irc_event_kick {
+	char *origin;
+	char *channel;
+	char *target;
+	char *reason;
 };
 
-struct irc_event_msg {
-	char *prefix;
-	char *cmd;
-	char *args[IRC_ARGS_MAX];
-	char buf[IRC_MESSAGE_MAX];
+struct irc_event_message {
+	char *origin;
+	char *channel;
+	char *message;
+};
+
+struct irc_event_mode {
+	char *origin;
+	char *channel;
+	char *mode;
+	char *limit;
+	char *user;
+	char *mask;
+};
+
+struct irc_event_names {
+	char *channel;
+	char *names;
+};
+
+struct irc_event_nick {
+	char *origin;
+	char *nickname;
+};
+
+struct irc_event_notice {
+	char *origin;
+	char *channel;
+	char *notice;
+};
+
+struct irc_event_part {
+	char *origin;
+	char *channel;
+	char *reason;
+};
+
+struct irc_event_topic {
+	char *origin;
+	char *channel;
+	char *topic;
+};
+
+struct irc_event_whois {
+	char *nickname;
+	char *username;
+	char *realname;
+	char *hostname;
+	char *channels;
 };
 
 struct irc_event {
 	enum irc_event_type type;
 	struct irc_server *server;
 	union {
-		struct irc_event_msg msg;
-		struct irc_server_whois *whois;
+		struct irc_event_invite invite;
+		struct irc_event_join join;
+		struct irc_event_kick kick;
+		struct irc_event_message message;
+		struct irc_event_mode mode;
+		struct irc_event_names names;
+		struct irc_event_nick nick;
+		struct irc_event_notice notice;
+		struct irc_event_part part;
+		struct irc_event_topic topic;
+		struct irc_event_whois whois;
 	};
 };
 
 bool
-irc_event_is_ctcp(const char *);
-
-char *
-irc_event_ctcp(char *);
-
-bool
-irc_event_parse(struct irc_event_msg *, const char *);
-
-bool
 irc_event_str(const struct irc_event *, char *, size_t);
 
+void
+irc_event_finish(struct irc_event *);
+
 #endif /* !IRCCD_EVENT_H */
--- a/lib/irccd/irccd.c	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/irccd.c	Thu Jan 21 15:34:25 2021 +0100
@@ -76,22 +76,26 @@
 	cc = ev->server->commandchar;
 	ccsz = strlen(cc);
 
-	return strncmp(ev->msg.args[1], cc, ccsz) == 0 &&
-	       strncmp(ev->msg.args[1] + ccsz, p->name, strlen(p->name)) == 0;
+	return strncmp(ev->message.message, cc, ccsz) == 0 &&
+	       strncmp(ev->message.message + ccsz, p->name, strlen(p->name)) == 0;
 }
 
 static struct irc_event *
 to_command(const struct irc_plugin *p, struct irc_event *ev)
 {
-	char *s;
+	char *action;
+
+	/* Convert "!test foo bar" to "foo bar" */
+	action = ev->message.message + strlen(ev->server->commandchar) + strlen(p->name);
+
+	while (*action && isspace(*action))
+		++action;
+
+	action = strdup(action);
+	free(ev->message.message);
 
 	ev->type = IRC_EVENT_COMMAND;
-	ev->msg.args[1] = ev->msg.args[1] + strlen(ev->server->commandchar) + strlen(p->name);
-
-	for (s = ev->msg.args[1]; *s && isspace(*s); )
-		++s;
-
-	ev->msg.args[1] = s;
+	ev->message.message = action;
 
 	return ev;
 }
@@ -102,7 +106,7 @@
 	switch (ev->type) {
 	case IRC_EVENT_COMMAND:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onCommand");
+		    ev->message.channel, ev->message.origin, p->name, "onCommand");
 	case IRC_EVENT_CONNECT:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
 		    NULL, NULL, p->name, "onConnect");
@@ -111,40 +115,41 @@
 		    NULL, NULL, p->name, "onDisconnect");
 	case IRC_EVENT_INVITE:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[1], ev->msg.prefix, p->name, "onInvite");
+		    ev->invite.channel, ev->invite.origin, p->name, "onInvite");
 	case IRC_EVENT_JOIN:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onJoin");
+		    ev->join.channel, ev->join.origin, p->name, "onJoin");
 	case IRC_EVENT_KICK:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onKick");
+		    ev->kick.channel, ev->kick.origin, p->name, "onKick");
 		break;
 	case IRC_EVENT_ME:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onMe");
+		    ev->message.channel, ev->message.origin, p->name, "onMe");
 	case IRC_EVENT_MESSAGE:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onMessage");
+		    ev->message.channel, ev->message.origin, p->name, "onMessage");
 	case IRC_EVENT_MODE:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onMode");
+		    ev->mode.channel, ev->mode.origin, p->name, "onMode");
 	case IRC_EVENT_NAMES:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], NULL, p->name, "onNames");
+		    ev->names.channel, NULL, p->name, "onNames");
 	case IRC_EVENT_NICK:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    NULL, ev->msg.prefix, p->name, "onNick");
+		    NULL, ev->nick.origin, p->name, "onNick");
 	case IRC_EVENT_NOTICE:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onNotice");
+		    ev->notice.channel, ev->notice.origin, p->name, "onNotice");
 	case IRC_EVENT_PART:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onPart");
+		    ev->part.channel, ev->part.origin, p->name, "onPart");
 	case IRC_EVENT_TOPIC:
 		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
-		    ev->msg.args[0], ev->msg.prefix, p->name, "onTopic");
+		    ev->topic.channel, ev->topic.origin, p->name, "onTopic");
 	case IRC_EVENT_WHOIS:
-		return true;
+		return irc_rule_matchlist(irc.rules, irc.rulesz, ev->server->name,
+		    NULL, NULL, p->name, "onWhois");
 	default:
 		return true;
 	}
@@ -278,6 +283,7 @@
 		while (irc_server_poll(s, &ev)) {
 			broadcast(&ev);
 			invoke(&ev);
+			irc_event_finish(&ev);
 		}
 	}
 }
--- a/lib/irccd/js-plugin.c	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/js-plugin.c	Thu Jan 21 15:34:25 2021 +0100
@@ -87,12 +87,12 @@
 static void
 push_names(duk_context *ctx, const struct irc_event *ev)
 {
-	const struct irc_channel *ch = irc_server_find(ev->server, ev->msg.args[0]);
+	char *token, *p = ev->names.names;
 
 	duk_push_array(ctx);
 
-	for (size_t i = 0; i < ch->usersz; ++i) {
-		duk_push_string(ctx, ch->users[i].nickname);
+	for (size_t i = 0; (token = strtok_r(p, " ", &p)); ++i) {
+		duk_push_string(ctx, irc_server_strip(ev->server, token).name);
 		duk_put_prop_index(ctx, -2, i);
 	}
 }
@@ -100,22 +100,25 @@
 static void
 push_whois(duk_context *ctx, const struct irc_event *ev)
 {
+	char *token, *p = ev->whois.channels;
+	struct irc_server_namemode nm;
+
 	duk_push_object(ctx);
-	duk_push_string(ctx, ev->whois->nickname);
+	duk_push_string(ctx, ev->whois.nickname);
 	duk_put_prop_string(ctx, -2, "nickname");
-	duk_push_string(ctx, ev->whois->username);
+	duk_push_string(ctx, ev->whois.username);
 	duk_put_prop_string(ctx, -2, "username");
-	duk_push_string(ctx, ev->whois->realname);
+	duk_push_string(ctx, ev->whois.realname);
 	duk_put_prop_string(ctx, -2, "realname");
-	duk_push_string(ctx, ev->whois->hostname);
+	duk_push_string(ctx, ev->whois.hostname);
 	duk_put_prop_string(ctx, -2, "hostname");
 	duk_push_array(ctx);
-	for (size_t i = 0; i < ev->whois->channelsz; ++i) {
-		printf("[%s] = [%c]\n", ev->whois->channels[i].channel, ev->whois->channels[i].mode);
+	for (size_t i = 0; (token = strtok_r(p, " ", &p)); ++i) {
+		nm = irc_server_strip(ev->server, token);
 		duk_push_object(ctx);
-		duk_push_string(ctx, ev->whois->channels[i].channel);
+		duk_push_string(ctx, nm.name);
 		duk_put_prop_string(ctx, -2, "channel");
-		duk_push_sprintf(ctx, "%c", ev->whois->channels[i].mode);
+		duk_push_sprintf(ctx, "%c", nm.mode);
 		duk_put_prop_string(ctx, -2, "mode");
 		duk_put_prop_index(ctx, -2, i);
 	}
@@ -304,8 +307,8 @@
 
 	switch (ev->type) {
 	case IRC_EVENT_COMMAND:
-		call(plg, "onCommand", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onCommand", "Ss ss", ev->server, ev->message.origin,
+		    ev->message.channel, ev->message.message);
 		break;
 	case IRC_EVENT_CONNECT:
 		call(plg, "onConnect", "S", ev->server);
@@ -314,49 +317,49 @@
 		call(plg, "onDisconnect", "S", ev->server);
 		break;
 	case IRC_EVENT_INVITE:
-		call(plg, "onInvite", "Ss s", ev->server, ev->msg.prefix,
-		     ev->msg.args[1]);
+		call(plg, "onInvite", "Ss s", ev->server, ev->invite.origin,
+		     ev->invite.channel);
 		break;
 	case IRC_EVENT_JOIN:
-		call(plg, "onJoin", "Ss s", ev->server, ev->msg.prefix,
-		    ev->msg.args[0]);
+		call(plg, "onJoin", "Ss s", ev->server, ev->join.origin,
+		    ev->join.channel);
 		break;
 	case IRC_EVENT_KICK:
-		call(plg, "onKick", "Ss sss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1], ev->msg.args[2]);
+		call(plg, "onKick", "Ss sss", ev->server, ev->kick.origin,
+		    ev->kick.channel, ev->kick.target, ev->kick.reason);
 		break;
 	case IRC_EVENT_ME:
-		call(plg, "onMe", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onMe", "Ss ss", ev->server, ev->message.origin,
+		    ev->message.channel, ev->message.message);
 		break;
 	case IRC_EVENT_MESSAGE:
-		call(plg, "onMessage", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onMessage", "Ss ss", ev->server, ev->message.origin,
+		    ev->message.channel, ev->message.message);
 		break;
 	case IRC_EVENT_MODE:
-		call(plg, "onMode", "Ss sss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1], ev->msg.args[2],
-		    ev->msg.args[3], ev->msg.args[4]);
+		call(plg, "onMode", "Ss sss ss", ev->server, ev->mode.origin,
+		    ev->mode.channel, ev->mode.mode, ev->mode.limit,
+		    ev->mode.user, ev->mode.mask);
 		break;
 	case IRC_EVENT_NAMES:
-		call(plg, "onNames", "Ss x", ev->server, ev->msg.args[1],
+		call(plg, "onNames", "Ss x", ev->server, ev->names.channel,
 		    push_names, ev);
 		break;
 	case IRC_EVENT_NICK:
-		call(plg, "onNick", "Ss s", ev->server, ev->msg.prefix,
-		    ev->msg.args[0]);
+		call(plg, "onNick", "Ss s", ev->server, ev->nick.origin,
+		    ev->nick.nickname);
 		break;
 	case IRC_EVENT_NOTICE:
-		call(plg, "onNotice", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onNotice", "Ss ss", ev->server, ev->notice.origin,
+		    ev->notice.channel, ev->notice.notice);
 		break;
 	case IRC_EVENT_PART:
-		call(plg, "onPart", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onPart", "Ss ss", ev->server, ev->part.origin,
+		    ev->part.channel, ev->part.reason);
 		break;
 	case IRC_EVENT_TOPIC:
-		call(plg, "onTopic", "Ss ss", ev->server, ev->msg.prefix,
-		    ev->msg.args[0], ev->msg.args[1]);
+		call(plg, "onTopic", "Ss ss", ev->server, ev->topic.origin,
+		    ev->topic.channel, ev->topic.topic);
 		break;
 	case IRC_EVENT_WHOIS:
 		call(plg, "onWhois", "Sx", ev->server, push_whois, ev);
--- a/lib/irccd/server.c	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/server.c	Thu Jan 21 15:34:25 2021 +0100
@@ -49,12 +49,85 @@
 	char host[IRC_HOST_MAX];
 };
 
+struct message {
+	char *prefix;
+	char *cmd;
+	char *args[IRC_ARGS_MAX];
+	char buf[IRC_MESSAGE_MAX];
+};
+
+static inline void
+scan(char **line, char **str)
+{
+	char *p = strchr(*line, ' ');
+
+	if (p)
+		*p = '\0';
+
+	*str = *line;
+	*line = p ? p + 1 : strchr(*line, '\0');
+}
+
+static bool
+parse(struct message *msg, const char *line)
+{
+	char *ptr = msg->buf;
+	size_t a;
+
+	memset(msg, 0, sizeof (*msg));
+	strlcpy(msg->buf, line, sizeof (msg->buf));
+
+	/*
+	 * IRC message is defined as following:
+	 *
+	 * [:prefix] command arg1 arg2 [:last-argument]
+	 */
+	if (*ptr == ':')
+		scan((++ptr, &ptr), &msg->prefix);     /* prefix */
+
+	scan(&ptr, &msg->cmd);                         /* command */
+
+	/* And finally arguments. */
+	for (a = 0; *ptr && a < IRC_ARGS_MAX; ++a) {
+		if (*ptr == ':') {
+			msg->args[a] = ptr + 1;
+			ptr = strchr(ptr, '\0');
+		} else
+			scan(&ptr, &msg->args[a]);
+	}
+
+	if (a >= IRC_ARGS_MAX)
+		return errno = EMSGSIZE, false;
+	if (msg->cmd == NULL)
+		return errno = EBADMSG, false;
+
+	return true;
+}
+
+static inline char
+sym(const struct irc_server *s, char mode)
+{
+	for (size_t i = 0; i < sizeof (s->prefixes); ++i)
+		if (s->prefixes[i].mode == mode)
+			return s->prefixes[i].token;
+
+	return '?';
+}
+
+static inline bool
+is_self(const struct irc_server *s, const char *nick)
+{
+	return strncmp(s->nickname, nick, strlen(s->nickname)) == 0;
+}
+
 static int
 cmp_channel(const struct irc_channel *c1, const struct irc_channel *c2)
 {
 	return strcmp(c1->name, c2->name);
 }
 
+#if 0
+
 static const struct origin *
 parse_origin(const char *prefix)
 {
@@ -71,6 +144,8 @@
 	return &origin;
 }
 
+#endif
+
 static void
 add_nick(const struct irc_server *s, struct irc_channel *ch, const char *nick)
 {
@@ -116,6 +191,35 @@
 	IRC_SET_ALLOC_REMOVE(&s->channels, &s->channelsz, ch);
 }
 
+bool
+is_ctcp(const char *line)
+{
+	size_t length;
+
+	if (!line)
+		return false;
+	if ((length = strlen(line)) < 2)
+		return false;
+
+	return line[0] == 0x1 && line[length - 1] == 0x1;
+}
+
+char *
+ctcp(char *line)
+{
+	/* Skip first \001. */
+	if (*line == '\001')
+		line++;
+
+	/* Remove last \001. */
+	line[strcspn(line, "\001")] = '\0';
+
+	if (strncmp(line, "ACTION ", 7) == 0)
+		line += 7;
+
+	return line;
+}
+
 static void
 read_support_prefix(struct irc_server *s, const char *value)
 {
@@ -140,8 +244,10 @@
 }
 
 static void
-handle_connect(struct irc_server *s, struct irc_event *ev)
+handle_connect(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
+	(void)msg;
+
 	s->state = IRC_SERVER_STATE_CONNECTED;
 
 	/* Now join all channels that were requested. */
@@ -152,15 +258,15 @@
 }
 
 static void
-handle_support(struct irc_server *s, struct irc_event *ev)
+handle_support(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)ev;
 
 	char key[64];
 	char value[64];
 
-	for (size_t i = 2; ev->msg.args[i]; ++i) {
-		if (sscanf(ev->msg.args[i], "%63[^=]=%63s", key, value) != 2)
+	for (size_t i = 0; i < IRC_UTIL_SIZE(msg->args) && msg->args[i]; ++i) {
+		if (sscanf(msg->args[i], "%63[^=]=%63s", key, value) != 2)
 			continue;
 
 		if (strcmp(key, "PREFIX") == 0)
@@ -171,191 +277,259 @@
 }
 
 static void
-handle_invite(struct irc_server *s, struct irc_event *ev)
+handle_invite(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	if (strcmp(ev->msg.args[0], s->nickname) == 0 && s->flags & IRC_SERVER_FLAGS_JOIN_INVITE)
-		irc_server_join(s, ev->msg.args[1], NULL);
+	ev->type = IRC_EVENT_INVITE;
+	ev->invite.origin = strdup(msg->args[0]);
+	ev->invite.channel = strdup(msg->args[1]);
+	ev->invite.target = strdup(msg->args[2]);
 
-	ev->type = IRC_EVENT_INVITE;
+	if (is_self(s, ev->invite.target) && s->flags & IRC_SERVER_FLAGS_JOIN_INVITE)
+		irc_server_join(s, ev->invite.channel, NULL);
 }
 
 static void
-handle_join(struct irc_server *s, struct irc_event *ev)
+handle_join(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	add_channel(s, ev->msg.args[0], NULL, true);
+	ev->type = IRC_EVENT_JOIN;
+	ev->join.origin = strdup(msg->prefix);
+	ev->join.channel = strdup(msg->args[0]);
 
-	ev->type = IRC_EVENT_JOIN;
+	add_channel(s, ev->join.channel, NULL, true);
 }
 
 static void
-handle_kick(struct irc_server *s, struct irc_event *ev)
+handle_kick(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	struct irc_channel *ch = add_channel(s, ev->msg.args[0], NULL, true);
+	ev->type = IRC_EVENT_KICK;
+	ev->kick.origin = strdup(msg->prefix);
+	ev->kick.channel = strdup(msg->args[0]);
+	ev->kick.target = strdup(msg->args[1]);
+	ev->kick.reason = msg->args[2] ? strdup(msg->args[2]) : NULL;
+
+	struct irc_channel *ch = add_channel(s, ev->kick.channel, NULL, true);
 
 	/*
 	 * If the bot was kicked itself mark the channel as not joined and
 	 * rejoin it automatically if the option is set.
 	 */
-	if (strcmp(ev->msg.args[1], s->nickname) == 0) {
+	if (is_self(s, ev->kick.target) == 0) {
 		ch->joined = false;
 		irc_channel_clear(ch);
 
 		if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN)
 			irc_server_join(s, ch->name, ch->password);
 	} else
-		irc_channel_remove(ch, ev->msg.args[1]);
-
-	ev->type = IRC_EVENT_KICK;
+		irc_channel_remove(ch, ev->kick.target);
 }
 
 static void
-handle_mode(struct irc_server *s, struct irc_event *ev)
+handle_mode(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)s;
 	(void)ev;
+	(void)msg;
 
 	ev->type = IRC_EVENT_MODE;
+	ev->mode.origin = strdup(msg->prefix);
+	ev->mode.channel = strdup(msg->args[0]);
+	ev->mode.mode = strdup(msg->args[1]);
+	ev->mode.limit = msg->args[2] ? strdup(msg->args[2]) : NULL;
+	ev->mode.user = msg->args[3] ? strdup(msg->args[3]) : NULL;
+	ev->mode.mask = msg->args[4] ? strdup(msg->args[4]) : NULL;
+
+	/* TODO: update nickname modes. */
 }
 
 static void
-handle_part(struct irc_server *s, struct irc_event *ev)
+handle_part(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	const struct origin *origin = parse_origin(ev->msg.prefix);
-	struct irc_channel *ch = add_channel(s, ev->msg.args[0], NULL, true);
-
-	if (strcmp(origin->nickname, s->nickname) == 0)
-		remove_channel(s, ch);
+	struct irc_channel *ch;
 
 	ev->type = IRC_EVENT_PART;
+	ev->part.origin = strdup(msg->prefix);
+	ev->part.channel = strdup(msg->args[0]);
+	ev->part.reason = msg->args[1] ? strdup(msg->args[1]) : NULL;
+
+	ch = add_channel(s, ev->part.channel, NULL, true);
+
+	if (is_self(s, ev->part.origin) == 0)
+		remove_channel(s, ch);
+	else
+		irc_channel_remove(ch, ev->part.origin);
 }
 
 static void
-handle_msg(struct irc_server *s, struct irc_event *ev)
+handle_msg(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)s;
 
+	ev->message.origin = strdup(msg->prefix);
+	ev->message.channel = strdup(msg->args[0]);
+
 	/*
 	 * Detect CTCP commands which are PRIVMSG with a special boundaries.
 	 *
 	 * Example:
 	 * PRIVMSG jean :\001ACTION I'm eating\001.
 	 */
-	if (irc_event_is_ctcp(ev->msg.args[1])) {
+	if (is_ctcp(msg->args[1])) {
 		ev->type = IRC_EVENT_ME;
-		ev->msg.args[1] = irc_event_ctcp(ev->msg.args[1]);
-	} else
+		ev->message.message = strdup(ctcp(msg->args[1]));
+	} else {
 		ev->type = IRC_EVENT_MESSAGE;
+		ev->message.message = strdup(msg->args[1]);
+	}
 }
 
 static void
-handle_nick(struct irc_server *s, struct irc_event *ev)
+handle_nick(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	const struct origin *origin = parse_origin(ev->msg.prefix);
+	ev->type = IRC_EVENT_NICK;
+	ev->nick.origin = strdup(msg->prefix);
+	ev->nick.nickname = strdup(msg->args[0]);
 
 	/* Update nickname if it is myself. */
-	if (strcmp(origin->nickname, s->nickname) == 0)
-		strlcpy(s->nickname, ev->msg.args[0], sizeof (s->nickname));
-
-	ev->type = IRC_EVENT_NICK;
+	if (is_self(s, ev->nick.origin) == 0)
+		strlcpy(s->nickname, ev->nick.nickname, sizeof (s->nickname));
 }
 
 static void
-handle_notice(struct irc_server *s, struct irc_event *ev)
+handle_notice(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)s;
 
 	ev->type = IRC_EVENT_NOTICE;
+	ev->notice.origin = strdup(msg->prefix);
+	ev->notice.channel = strdup(msg->args[0]);
+	ev->notice.notice = strdup(msg->args[1]);
 }
 
 static void
-handle_topic(struct irc_server *s, struct irc_event *ev)
+handle_topic(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)s;
 
 	ev->type = IRC_EVENT_TOPIC;
-}
-
-static void
-handle_ping(struct irc_server *s, struct irc_event *ev)
-{
-	irc_server_send(s, "PONG %s", ev->msg.args[0]);
+	ev->topic.origin = strdup(msg->prefix);
+	ev->topic.channel = strdup(msg->args[0]);
+	ev->topic.topic = strdup(msg->args[1]);
 }
 
 static void
-handle_names(struct irc_server *s, struct irc_event *ev)
+handle_ping(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
+	(void)s;
+	(void)ev;
+	(void)msg;
+
+	//irc_server_send(s, "PONG %s", args[1]);
+}
+
+static void
+handle_names(struct irc_server *s, struct irc_event *ev, struct message *msg)
+{
+	(void)s;
+	(void)ev;
+	(void)msg;
+
 	struct irc_channel *ch;
 	char *p, *token;
 
-	ch = add_channel(s, ev->msg.args[2], NULL, true);
+	ch = add_channel(s, msg->args[2], NULL, true);
 
-	/* TODO: libcompat for strtok_r. */
-	for (p = ev->msg.args[3]; (token = strtok_r(p, " ", &p)); )
+	/* Track existing nicknames into the given channel. */
+	for (p = msg->args[3]; (token = strtok_r(p, " ", &p)); )
 		if (strlen(token) > 0)
 			add_nick(s, ch, token);
 }
 
 static void
-handle_endofnames(struct irc_server *s, struct irc_event *ev)
+handle_endofnames(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
 	(void)s;
+	(void)ev;
+	(void)msg;
+
+	FILE *fp;
+	const struct irc_channel *ch;
+	size_t length;
 
 	ev->type = IRC_EVENT_NAMES;
-}
+	ev->names.channel = strdup(msg->args[1]);
+
+	/* Construct a string list for every user in the channel. */
+	ch = irc_server_find(s, ev->names.channel);
+	fp = open_memstream(&ev->names.names, &length);
+
+	for (size_t i = 0; i < ch->usersz; ++i) {
+		const struct irc_channel_user *u = &ch->users[i];
 
-static void
-handle_whoisuser(struct irc_server *s, struct irc_event *ev)
-{
-	(void)ev;
+		if (u->mode)
+			fprintf(fp, "%c", sym(s, u->mode));
+
+		fprintf(fp, "%s", u->nickname);
 
-	strlcpy(s->whois.nickname, ev->msg.args[1], sizeof (s->whois.nickname));
-	strlcpy(s->whois.username, ev->msg.args[2], sizeof (s->whois.username));
-	strlcpy(s->whois.hostname, ev->msg.args[3], sizeof (s->whois.hostname));
-	strlcpy(s->whois.realname, ev->msg.args[5], sizeof (s->whois.realname));
+		if (i + 1 < ch->usersz)
+			fputc(' ', fp);
+	}
+
+	fclose(fp);
 }
 
 static void
-add_whois_channel(struct irc_server *s, const char *channel)
+handle_whoisuser(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	char mode = 0;
-
-	s->whois.channels = irc_util_reallocarray(s->whois.channels,
-	    ++s->whois.channelsz, sizeof (*s->whois.channels));
+	(void)s;
+	(void)msg;
 
-	/* TODO: split this to refactor add_nick. */
-	for (size_t i = 0; i < IRC_UTIL_SIZE(s->prefixes); ++i) {
-		if (channel[0] == s->prefixes[i].token) {
-			mode = s->prefixes[i].mode;
-			++channel;
-			break;
-		}
-	}
-
-	s->whois.channels[s->whois.channelsz - 1].mode = mode;
-	strlcpy(s->whois.channels[s->whois.channelsz - 1].channel, channel,
-	    sizeof (s->whois.channels[0].channel));
+	s->bufwhois.nickname = strdup(msg->args[1]);
+	s->bufwhois.username = strdup(msg->args[2]);
+	s->bufwhois.hostname = strdup(msg->args[3]);
+	s->bufwhois.realname = strdup(msg->args[5]);
 }
 
 static void
-handle_whoischannels(struct irc_server *s, struct irc_event *ev)
+handle_whoischannels(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	char *p, *token;
+	(void)ev;
+
+	size_t curlen, reqlen;
+
+	curlen = s->bufwhois.channels ? strlen(s->bufwhois.channels) : 0;
+	reqlen = strlen(msg->args[2]);
 
-	for (p = ev->msg.args[2]; (token = strtok_r(p, " ", &p)); )
-		if (strlen(token) > 0)
-			add_whois_channel(s, token);
+	/*
+	 * If there is already something, add a space at the end of the current
+	 * buffer.
+	 */
+	if (curlen > 0)
+		reqlen++;
+
+	/* Now, don't forget */
+	s->bufwhois.channels = irc_util_realloc(s->bufwhois.channels, reqlen + 1);
+
+	if (curlen > 0) {
+		strcat(s->bufwhois.channels, " ");
+		strcat(s->bufwhois.channels, msg->args[2]);
+	} else
+		strcpy(s->bufwhois.channels, msg->args[2]);
 }
 
 static void
-handle_endofwhois(struct irc_server *s, struct irc_event *ev)
+handle_endofwhois(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
+	(void)msg;
+
 	ev->type = IRC_EVENT_WHOIS;
-	ev->whois = &s->whois;
+	ev->whois = s->bufwhois;
+
+	memset(&s->bufwhois, 0, sizeof (s->bufwhois));
 }
 
 static const struct handler {
 	const char *command;
-	void (*handle)(struct irc_server *, struct irc_event *);
+	void (*handle)(struct irc_server *, struct irc_event *, struct message *);
 } handlers[] = {
 	/* Must be kept ordered. */
 	{ "001",        handle_connect          },
@@ -378,21 +552,30 @@
 };
 
 static int
-compare_handler(const void *d1, const void *d2)
+cmp_handler(const char *name, const struct handler *handler)
 {
-	return strcmp(d1, ((const struct handler *)d2)->command);
+	return strcmp(name, handler->command);
+}
+
+static inline struct handler *
+find_handler(const char *name)
+{
+	return bsearch(name, handlers, IRC_UTIL_SIZE(handlers), sizeof (struct handler),
+	    (irc_cmp)(cmp_handler));
 }
 
 static void
-handle(struct irc_server *s, struct irc_event *ev)
+handle(struct irc_server *s, struct irc_event *ev, struct message *msg)
 {
-	const struct handler *c = bsearch(ev->msg.cmd, handlers, IRC_UTIL_SIZE(handlers),
-	    sizeof (*c), &(compare_handler));
+	const struct handler *h;
 
-	if (c) {
-		ev->server = s;
-		c->handle(s, ev);
-	}
+	if (!(h = find_handler(msg->cmd)))
+		return;
+
+	memset(ev, 0, sizeof (*ev));
+
+	ev->server = s;
+	h->handle(s, ev, msg);
 }
 
 static void
@@ -465,18 +648,14 @@
 
 	assert(s);
 
-	int r;
-
 	if (!(s->flags & IRC_SERVER_FLAGS_SSL))
 		return;
 
-	switch ((r = SSL_get_error(s->ssl, ret))) {
+	switch (SSL_get_error(s->ssl, ret)) {
 	case SSL_ERROR_WANT_READ:
-		printf("new ssl state: %d\n", s->ssl_state);
 		s->ssl_state = IRC_SERVER_SSL_NEED_READ;
 		break;
 	case SSL_ERROR_WANT_WRITE:
-		printf("new ssl state: %d\n", s->ssl_state);
 		s->ssl_state = IRC_SERVER_SSL_NEED_WRITE;
 		break;
 	case SSL_ERROR_SSL:
@@ -538,7 +717,8 @@
 {
 	int cflags = 0;
 
-	if ((cflags = fcntl(s->fd, F_GETFL)) < 0 || fcntl(s->fd, F_SETFL, cflags | O_NONBLOCK) < 0)
+	if ((cflags = fcntl(s->fd, F_GETFL)) < 0 ||
+	    fcntl(s->fd, F_SETFL, cflags | O_NONBLOCK) < 0)
 		return false;
 
 	return true;
@@ -812,6 +992,7 @@
 	assert(s);
 	assert(ev);
 
+	struct message msg;
 	char *pos;
 	size_t length;
 
@@ -822,12 +1003,8 @@
 	*pos = 0;
 	length = pos - s->in;
 
-	/* Clear event in case we don't understand this message. */
-	memset(ev, 0, sizeof (*ev));
-	ev->type = IRC_EVENT_UNKNOWN;
-
-	if (length > 0 && irc_event_parse(&ev->msg, s->in))
-		handle(s, ev);
+	if (length > 0 && parse(&msg, s->in))
+		handle(s, ev, &msg);
 
 	memmove(s->in, pos + 2, sizeof (s->in) - (length + 2));
 
@@ -1028,13 +1205,36 @@
 	assert(s);
 	assert(target);
 
+#if 0
 	/* Cleanup previous result. */
 	free(s->whois.channels);
 	memset(&s->whois, 0, sizeof (s->whois));
+#endif
 
 	return irc_server_send(s, "WHOIS %s", target);
 }
 
+struct irc_server_namemode
+irc_server_strip(const struct irc_server *s, const char *item)
+{
+	assert(s);
+	assert(item);
+
+	struct irc_server_namemode ret = {0};
+
+	for (size_t i = 0; i < IRC_UTIL_SIZE(s->prefixes); ++i) {
+		if (item[0] == s->prefixes[i].token) {
+			ret.mode = s->prefixes[i].mode;
+			++item;
+			break;
+		}
+	}
+
+	ret.name = (char *)item;
+
+	return ret;
+}
+
 void
 irc_server_incref(struct irc_server *s)
 {
@@ -1051,7 +1251,7 @@
 
 	if (--s->refc == 0) {
 		clear(s);
-		free(s->whois.channels);
+		//free(s->whois.channels);
 		free(s->channels);
 		free(s);
 	}
--- a/lib/irccd/server.h	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/server.h	Thu Jan 21 15:34:25 2021 +0100
@@ -28,12 +28,12 @@
 #       include <openssl/ssl.h>
 #endif
 
+#include "event.h"
 #include "limits.h"
 
 struct pollfd;
 
 struct irc_channel;
-struct irc_event;
 
 enum irc_server_state {
 	IRC_SERVER_STATE_DISCONNECTED,
@@ -67,16 +67,10 @@
 
 #endif
 
-struct irc_server_whois {
-	char nickname[IRC_NICKNAME_MAX];
-	char username[IRC_USERNAME_MAX];
-	char realname[IRC_REALNAME_MAX];
-	char hostname[IRC_HOST_MAX];
-	struct {
-		char channel[IRC_CHANNEL_MAX];
-		char mode;
-	} *channels;
-	size_t channelsz;
+struct irc_server_namemode {
+	char mode;
+	char sym;
+	char *name;
 };
 
 struct irc_server {
@@ -115,8 +109,7 @@
 	enum irc_server_ssl_state ssl_state;
 #endif
 
-	/* Whois being stored. */
-	struct irc_server_whois whois;
+	struct irc_event_whois bufwhois;
 
 	/* Reference count. */
 	size_t refc;
@@ -190,6 +183,9 @@
 bool
 irc_server_whois(struct irc_server *, const char *);
 
+struct irc_server_namemode
+irc_server_strip(const struct irc_server *, const char *);
+
 void
 irc_server_incref(struct irc_server *);
 
--- a/lib/irccd/util.h	Thu Jan 21 09:49:42 2021 +0100
+++ b/lib/irccd/util.h	Thu Jan 21 15:34:25 2021 +0100
@@ -23,6 +23,9 @@
 
 #define IRC_UTIL_SIZE(x) (sizeof (x) / sizeof (x[0]))
 
+/* Suitable convenient typedef for bsearch/qsort. */
+typedef int (*irc_cmp)(const void *, const void *);
+
 void *
 irc_util_malloc(size_t);