changeset 959:0d6e2a89fee5

irccd: split network code from server into conn.[ch]
author David Demelier <markand@malikania.fr>
date Sun, 24 Jan 2021 18:31:50 +0100
parents 533639ec5e9c
children 4af3140f234d
files irccd/main.c lib/CMakeLists.txt lib/irccd/channel.h lib/irccd/conn.c lib/irccd/conn.h lib/irccd/event.c lib/irccd/js-plugin.c lib/irccd/jsapi-server.c lib/irccd/limits.h lib/irccd/server.c lib/irccd/server.h
diffstat 11 files changed, 799 insertions(+), 661 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.c	Thu Jan 21 23:15:20 2021 +0100
+++ b/irccd/main.c	Sun Jan 24 18:31:50 2021 +0100
@@ -34,4 +34,12 @@
 	(void)argc;
 	(void)argv;
 
+	struct irc_server *s;
+
+	irc_bot_init();
+
+	s = irc_server_new("mlk", "circ", "circ", "circ", "malikania.fr", 6667);
+	irc_server_join(s, "#test", NULL);
+	irc_bot_server_add(s);
+	irc_bot_run();
 }
--- a/lib/CMakeLists.txt	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/CMakeLists.txt	Sun Jan 24 18:31:50 2021 +0100
@@ -24,6 +24,8 @@
 	irccd/channel.c
 	irccd/channel.h
 	irccd/config.h.in
+	irccd/conn.c
+	irccd/conn.h
 	irccd/dl-plugin.c
 	irccd/dl-plugin.h
 	irccd/event.c
--- a/lib/irccd/channel.h	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/channel.h	Sun Jan 24 18:31:50 2021 +0100
@@ -40,6 +40,8 @@
 	LIST_ENTRY(irc_channel) link;
 };
 
+LIST_HEAD(irc_channel_list, irc_channel);
+
 struct irc_channel *
 irc_channel_new(const char *, const char *, bool);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/conn.c	Sun Jan 24 18:31:50 2021 +0100
@@ -0,0 +1,453 @@
+/*
+ * conn.c -- an IRC server channel
+ *
+ * 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 <sys/socket.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "conn.h"
+#include "util.h"
+
+static void
+cleanup(struct irc_conn *conn)
+{
+	if (conn->fd != 0)
+		close(conn->fd);
+
+#if defined(IRCCD_WITH_SSL)
+	if (conn->ssl)
+		SSL_free(conn->ssl);
+	if (conn->ctx)
+		SSL_CTX_free(conn->ctx);
+
+	conn->ssl = NULL;
+	conn->ctx = NULL;
+#endif
+
+	conn->fd = -1;
+}
+
+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 irc_conn_msg *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_UTIL_SIZE(msg->args); ++a) {
+		if (*ptr == ':') {
+			msg->args[a] = ptr + 1;
+			ptr = strchr(ptr, '\0');
+		} else
+			scan(&ptr, &msg->args[a]);
+	}
+
+	if (a >= IRC_UTIL_SIZE(msg->args))
+		return errno = EMSGSIZE, false;
+	if (msg->cmd == NULL)
+		return errno = EBADMSG, false;
+
+	return true;
+}
+
+static bool
+create(struct irc_conn *conn)
+{
+	struct addrinfo *ai = conn->aip;
+	int cflags = 0;
+
+	cleanup(conn);
+
+	if ((conn->fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
+		return false;
+	if ((cflags = fcntl(conn->fd, F_GETFL)) < 0)
+		return false;
+	if (fcntl(conn->fd, F_SETFL, cflags | O_NONBLOCK) < 0)
+		return false;
+
+	return true;
+}
+
+static inline bool
+update_ssl_state(struct irc_conn *conn, int ret)
+{
+	switch (SSL_get_error(conn->ssl, ret)) {
+	case SSL_ERROR_WANT_READ:
+		conn->ssl_state = IRC_CONN_SSL_STATE_NEED_READ;
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		conn->ssl_state = IRC_CONN_SSL_STATE_NEED_WRITE;
+		break;
+	case SSL_ERROR_SSL:
+		return irc_conn_disconnect(conn), -1;
+	default:
+		break;
+	}
+
+	return true;
+}
+
+static ssize_t
+input_ssl(struct irc_conn *conn, char *dst, size_t dstsz)
+{
+	int nr;
+
+	if ((nr = SSL_read(conn->ssl, dst, dstsz)) <= 0)
+		return update_ssl_state(conn, nr);
+
+	conn->ssl_state = IRC_CONN_SSL_STATE_NONE;
+
+	return nr;
+}
+
+static inline ssize_t
+input_clear(struct irc_conn *conn, char *buf, size_t bufsz)
+{
+	ssize_t nr;
+
+	if ((nr = recv(conn->fd, buf, bufsz, 0)) <= 0)
+		return irc_conn_disconnect(conn), -1;
+
+	return nr;
+}
+
+static bool
+input(struct irc_conn *conn)
+{
+	size_t len = strlen(conn->in);
+	size_t cap = sizeof (conn->in) - len - 1;
+	ssize_t nr = 0;
+
+	if (conn->flags & IRC_CONN_SSL)
+		nr = input_ssl(conn, conn->in + len, cap);
+	else
+		nr = input_clear(conn, conn->in + len, cap);
+
+	if (nr > 0)
+		conn->in[len + nr] = '\0';
+
+	return nr > 0;
+}
+
+static inline ssize_t
+output_ssl(struct irc_conn *conn)
+{
+	int ns;
+
+	if ((ns = SSL_write(conn->ssl, conn->out, strlen(conn->out))) <= 0)
+		return update_ssl_state(conn, ns);
+
+	return ns;
+}
+
+static inline ssize_t
+output_clear(struct irc_conn *conn)
+{
+	ssize_t ns;
+
+	if ((ns = send(conn->fd, conn->out, strlen(conn->out), 0)) < 0)
+		return irc_conn_disconnect(conn), -1;
+
+	return ns;
+}
+
+static bool
+output(struct irc_conn *conn)
+{
+	ssize_t ns = 0;
+
+	if (conn->flags & IRC_CONN_SSL)
+		ns = output_ssl(conn);
+	else
+		ns = output_clear(conn);
+
+	if (ns > 0) {
+		/* Optimize if everything was sent. */
+		if ((size_t)ns >= sizeof (conn->out) - 1)
+			conn->out[0] = '\0';
+		else
+			memmove(conn->out, conn->out + ns, sizeof (conn->out) - ns);
+	}
+
+	return ns != -1;
+}
+
+static bool
+handshake(struct irc_conn *conn)
+{
+	if (conn->flags & IRC_CONN_SSL) {
+#if defined(IRCCD_WITH_SSL)
+		int r;
+
+		conn->state = IRC_CONN_STATE_HANDSHAKING;
+
+		/*
+		 * This function is called several time until it completes so we
+		 * must keep the same context/ssl stuff once it has been
+		 * created.
+		 */
+		if (!conn->ctx)
+			conn->ctx = SSL_CTX_new(TLS_method());
+		if (!conn->ssl) {
+			conn->ssl = SSL_new(conn->ctx);
+
+			SSL_set_fd(conn->ssl, conn->fd);
+			SSL_set_connect_state(conn->ssl);
+		}
+
+		if ((r = SSL_do_handshake(conn->ssl)) <= 0)
+			return update_ssl_state(conn, r);
+
+		conn->state = IRC_CONN_STATE_READY;
+#endif
+	} else
+		conn->state = IRC_CONN_STATE_READY;
+
+	return true;
+}
+
+static bool
+dial(struct irc_conn *conn)
+{
+	/* No more address available. */
+	if (conn->aip == NULL)
+		return irc_conn_disconnect(conn), false;
+
+	for (; conn->aip; conn->aip = conn->aip->ai_next) {
+		if (!create(conn)) {
+			// irc_log_warn("server %s: %s", s->name, strerror(errno));
+			continue;
+		}
+
+		/*
+		 * With some luck, the connection completes immediately,
+		 * otherwise we will need to wait until the socket is writable.
+		 */
+		if (connect(conn->fd, conn->aip->ai_addr, conn->aip->ai_addrlen) == 0)
+			return handshake(conn);
+
+		/* Connect "succeeds" but isn't complete yet. */
+		if (errno == EINPROGRESS || errno == EAGAIN)
+			return true;
+	}
+
+	return false;
+}
+
+static bool
+lookup(struct irc_conn *conn)
+{
+	struct addrinfo hints = {
+		.ai_socktype = SOCK_STREAM,
+		.ai_flags = AI_NUMERICSERV
+	};
+	char service[16];
+	int ret;
+
+	snprintf(service, sizeof (service), "%hu", conn->port);
+
+	if ((ret = getaddrinfo(conn->hostname, service, &hints, &conn->ai)) != 0) {
+		// irc_log_warn gai_strerror(ret)
+		return false;
+	}
+
+	conn->aip = conn->ai;
+
+	return true;
+}
+
+static bool
+check_connect(struct irc_conn *conn)
+{
+	int res, err = -1;
+	socklen_t len = sizeof (int);
+
+	/* Determine if the non blocking connect(2) call succeeded. */
+	if ((res = getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &err, &len)) < 0 || err)
+		return dial(conn);
+
+	return handshake(conn);
+}
+
+bool
+irc_conn_connect(struct irc_conn *conn)
+{
+	assert(conn);
+
+	conn->state = IRC_CONN_STATE_CONNECTING;
+
+	if (!lookup(conn))
+		return irc_conn_disconnect(conn), false;
+
+	return dial(conn), true;
+}
+
+void
+irc_conn_disconnect(struct irc_conn *conn)
+{
+	assert(conn);
+
+	cleanup(conn);
+	conn->state = IRC_CONN_STATE_NONE;
+}
+
+static inline void
+prepare_ssl(const struct irc_conn *conn, struct pollfd *pfd)
+{
+#if defined(IRCCD_WITH_SSL)
+	switch (conn->ssl_state) {
+	case IRC_CONN_SSL_STATE_NEED_READ:
+		pfd->events |= POLLIN;
+		break;
+	case IRC_CONN_SSL_STATE_NEED_WRITE:
+		pfd->events |= POLLOUT;
+		break;
+	default:
+		break;
+	}
+#else
+	(void)conn;
+#endif
+}
+
+void
+irc_conn_prepare(const struct irc_conn *conn, struct pollfd *pfd)
+{
+	assert(conn);
+	assert(pfd);
+
+	pfd->fd = conn->fd;
+
+	if (conn->ssl_state)
+		prepare_ssl(conn, pfd);
+	else {
+		switch (conn->state) {
+		case IRC_CONN_STATE_CONNECTING:
+			pfd->events = POLLOUT;
+			break;
+		case IRC_CONN_STATE_READY:
+			pfd->events = POLLIN;
+
+			if (conn->out[0])
+				pfd->events |= POLLOUT;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+bool
+irc_conn_flush(struct irc_conn *conn, const struct pollfd *pfd)
+{
+	assert(conn);
+	assert(pfd);
+
+	switch (conn->state) {
+	case IRC_CONN_STATE_CONNECTING:
+		return check_connect(conn);
+	case IRC_CONN_STATE_HANDSHAKING:
+		return handshake(conn);
+	case IRC_CONN_STATE_READY:
+		if (pfd->revents & (POLLERR | POLLHUP))
+			return irc_conn_disconnect(conn), false;
+		if (pfd->revents & POLLIN && !input(conn))
+			return irc_conn_disconnect(conn), false;
+		if (pfd->revents & POLLOUT && !output(conn))
+			return irc_conn_disconnect(conn), false;
+		break;
+	default:
+		break;
+	}
+
+	return true;
+}
+
+bool
+irc_conn_poll(struct irc_conn *conn, struct irc_conn_msg *msg)
+{
+	assert(conn);
+	assert(msg);
+
+	char *pos;
+	size_t length;
+
+	if (!(pos = strstr(conn->in, "\r\n")))
+		return false;
+
+	/* Turn end of the string at delimiter. */
+	*pos = 0;
+	length = pos - conn->in;
+
+	if (length > 0)
+		parse(msg, conn->in);
+
+	memmove(conn->in, pos + 2, sizeof (conn->in) - (length + 2));
+
+	return true;
+}
+
+bool
+irc_conn_send(struct irc_conn *conn, const char *data)
+{
+	assert(conn);
+	assert(data);
+
+	if (strlcat(conn->out, data, sizeof (conn->out)) >= sizeof (conn->out))
+		return errno = EMSGSIZE, false;
+	if (strlcat(conn->out, "\r\n", sizeof (conn->out)) >= sizeof (conn->out))
+		return errno = EMSGSIZE, false;
+
+	return true;
+}
+
+void
+irc_conn_finish(struct irc_conn *conn)
+{
+	assert(conn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/conn.h	Sun Jan 24 18:31:50 2021 +0100
@@ -0,0 +1,102 @@
+/*
+ * conn.h -- an IRC server channel
+ *
+ * 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_CONN_H
+#define IRCCD_CONN_H
+
+#include <stdbool.h>
+
+#include "config.h"
+
+#if defined(IRCCD_WITH_SSL)
+#       include <openssl/ssl.h>
+#endif
+
+#include "limits.h"
+
+struct addrinfo;
+struct pollfd;
+
+enum irc_conn_state {
+	IRC_CONN_STATE_NONE,            /* Nothing, default. */
+	IRC_CONN_STATE_CONNECTING,      /* Pending connect(2) call. */
+	IRC_CONN_STATE_HANDSHAKING,     /* SSL connect+handshake. */
+	IRC_CONN_STATE_READY            /* Ready for I/O. */
+};
+
+enum irc_conn_flags {
+	IRC_CONN_SSL = (1 << 0)
+};
+
+#if defined(IRCCD_WITH_SSL)
+
+enum irc_conn_ssl_state {
+	IRC_CONN_SSL_STATE_NONE,
+	IRC_CONN_SSL_STATE_NEED_READ,
+	IRC_CONN_SSL_STATE_NEED_WRITE,
+};
+
+#endif
+
+struct irc_conn {
+	char hostname[IRC_HOST_LEN];
+	unsigned short port;
+	int fd;
+	struct addrinfo *ai;
+	struct addrinfo *aip;
+	char in[IRC_BUF_LEN];
+	char out[IRC_BUF_LEN];
+	enum irc_conn_state state;
+	enum irc_conn_flags flags;
+
+#if defined(IRCCD_WITH_SSL)
+	SSL_CTX *ctx;
+	SSL *ssl;
+	enum irc_conn_ssl_state ssl_state;
+#endif
+};
+
+struct irc_conn_msg {
+	char *prefix;
+	char *cmd;
+	char *args[IRC_ARGS_MAX];
+	char buf[IRC_MESSAGE_LEN];
+};
+
+bool
+irc_conn_connect(struct irc_conn *);
+
+void
+irc_conn_disconnect(struct irc_conn *);
+
+void
+irc_conn_prepare(const struct irc_conn *, struct pollfd *);
+
+bool
+irc_conn_flush(struct irc_conn *, const struct pollfd *);
+
+bool
+irc_conn_poll(struct irc_conn *, struct irc_conn_msg *);
+
+bool
+irc_conn_send(struct irc_conn *, const char *);
+
+void
+irc_conn_finish(struct irc_conn *);
+
+#endif /* !IRCCD_CONN_H */
--- a/lib/irccd/event.c	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/event.c	Sun Jan 24 18:31:50 2021 +0100
@@ -19,6 +19,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "event.h"
--- a/lib/irccd/js-plugin.c	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/js-plugin.c	Sun Jan 24 18:31:50 2021 +0100
@@ -87,12 +87,14 @@
 static void
 push_names(duk_context *ctx, const struct irc_event *ev)
 {
-	char *token, *p = ev->names.names;
+	const char *token;
+	char *p = ev->names.names;
 
 	duk_push_array(ctx);
 
 	for (size_t i = 0; (token = strtok_r(p, " ", &p)); ++i) {
-		duk_push_string(ctx, irc_server_strip(ev->server, token).name);
+		irc_server_strip(ev->server, &token, NULL, NULL);
+		duk_push_string(ctx, token);
 		duk_put_prop_index(ctx, -2, i);
 	}
 }
@@ -100,8 +102,8 @@
 static void
 push_whois(duk_context *ctx, const struct irc_event *ev)
 {
-	char *token, *p = ev->whois.channels;
-	struct irc_server_namemode nm;
+	const char *token;
+	char *p = ev->whois.channels;
 
 	duk_push_object(ctx);
 	duk_push_string(ctx, ev->whois.nickname);
@@ -114,12 +116,16 @@
 	duk_put_prop_string(ctx, -2, "hostname");
 	duk_push_array(ctx);
 	for (size_t i = 0; (token = strtok_r(p, " ", &p)); ++i) {
-		nm = irc_server_strip(ev->server, token);
+		char mode = 0, prefix = 0;
+
+		irc_server_strip(ev->server, &token, &mode, &prefix);
 		duk_push_object(ctx);
-		duk_push_string(ctx, nm.name);
+		duk_push_string(ctx, token);
 		duk_put_prop_string(ctx, -2, "channel");
-		duk_push_sprintf(ctx, "%c", nm.mode);
+		duk_push_sprintf(ctx, "%c", mode);
 		duk_put_prop_string(ctx, -2, "mode");
+		duk_push_sprintf(ctx, "%c", prefix);
+		duk_put_prop_string(ctx, -2, "prefix");
 		duk_put_prop_index(ctx, -2, i);
 	}
 	duk_put_prop_string(ctx, -2, "channels");
--- a/lib/irccd/jsapi-server.c	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/jsapi-server.c	Sun Jan 24 18:31:50 2021 +0100
@@ -60,16 +60,36 @@
 	return sv;
 }
 
-static inline void
-get_port(duk_context *ctx, struct irc_server *s)
+static inline unsigned short
+get_port(duk_context *ctx)
 {
+	unsigned short port;
+
 	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);
+	port = duk_to_int(ctx, -1);
 	duk_pop(ctx);
+
+	return port;
+}
+
+static inline const char *
+get_string(duk_context *ctx, const char *n)
+{
+	const char *ret;
+
+	duk_get_prop_string(ctx, 0, n);
+
+	if (!duk_is_string(ctx, -1))
+		duk_error(ctx, DUK_ERR_ERROR, "invalid or missing '%s' property", n);
+
+	ret = duk_to_string(ctx, -1);
+	duk_pop(ctx);
+
+	return ret;
 }
 
 static inline void
@@ -102,19 +122,6 @@
 }
 
 static inline void
-get_string(duk_context *ctx, const char *n, bool required, char *dst, size_t dstsz)
-{
-	duk_get_prop_string(ctx, 0, n);
-
-	if (duk_is_string(ctx, -1))
-		strlcpy(dst, duk_to_string(ctx, -1), dstsz);
-	else if (required)
-		duk_error(ctx, DUK_ERR_ERROR, "invalid or missing '%s' property", n);
-
-	duk_pop(ctx);
-}
-
-static inline void
 get_channels(duk_context *ctx, struct irc_server *s)
 {
 	duk_get_prop_string(ctx, 0, "channels");
@@ -144,19 +151,19 @@
 	duk_push_object(ctx);
 	duk_push_string(ctx, s->name);
 	duk_put_prop_string(ctx, -2, "name");
-	duk_push_string(ctx, s->hostname);
+	duk_push_string(ctx, s->conn.hostname);
 	duk_put_prop_string(ctx, -2, "hostname");
-	duk_push_uint(ctx, s->port);
+	duk_push_uint(ctx, s->conn.port);
 	duk_put_prop_string(ctx, -2, "port");
 	duk_push_boolean(ctx, s->flags & IRC_SERVER_FLAGS_SSL);
 	duk_put_prop_string(ctx, -2, "ssl");
 	duk_push_string(ctx, s->commandchar);
 	duk_put_prop_string(ctx, -2, "commandChar");
-	duk_push_string(ctx, s->realname);
+	duk_push_string(ctx, s->ident.realname);
 	duk_put_prop_string(ctx, -2, "realname");
-	duk_push_string(ctx, s->nickname);
+	duk_push_string(ctx, s->ident.nickname);
 	duk_put_prop_string(ctx, -2, "nickname");
-	duk_push_string(ctx, s->username);
+	duk_push_string(ctx, s->ident.username);
 	duk_put_prop_string(ctx, -2, "username");
 
 	duk_push_array(ctx);
@@ -441,26 +448,27 @@
 static duk_ret_t
 Server_constructor(duk_context *ctx)
 {
-	struct irc_server s = {0}, *p;
+	struct irc_server *s;
 
 	duk_require_object(ctx, 0);
 
-	get_string(ctx, "name", true, s.name, sizeof (s.name));
-	get_string(ctx, "hostname", true, s.hostname, sizeof (s.hostname));
-	get_port(ctx, &s);
-	get_ip(ctx, &s);
-	get_ssl(ctx, &s);
-	get_string(ctx, "nickname", false, s.nickname, sizeof (s.nickname));
-	get_string(ctx, "username", false, s.username, sizeof (s.username));
-	get_string(ctx, "realname", false, s.realname, sizeof (s.realname));
-	get_string(ctx, "commandChar", false, s.commandchar, sizeof (s.commandchar));
-	get_channels(ctx, &s);
+	s = irc_server_new(
+	    get_string(ctx, "name"),
+	    get_string(ctx, "nickname"),
+	    get_string(ctx, "username"),
+	    get_string(ctx, "realname"),
+	    get_string(ctx, "hostname"),
+	    get_port(ctx)
+	);
 
-	p = irc_util_memdup(&s, sizeof (s));
-	irc_server_incref(p);
+	get_ip(ctx, s);
+	get_ssl(ctx, s);
+	get_channels(ctx, s);
+
+	irc_server_incref(s);
 
 	duk_push_this(ctx);
-	duk_push_pointer(ctx, p);
+	duk_push_pointer(ctx, s);
 	duk_put_prop_string(ctx, -2, SIGNATURE);
 	duk_pop(ctx);
 
--- a/lib/irccd/limits.h	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/limits.h	Sun Jan 24 18:31:50 2021 +0100
@@ -27,7 +27,10 @@
 #define IRC_PASSWORD_LEN        64      /* Password length. */
 #define IRC_CTCPVERSION_LEN     64      /* Custom CTCP version answer. */
 #define IRC_USERMODES_LEN       8       /* Number of modes (e.g. ohv). */
+#define IRC_CHANTYPES_LEN       8
 #define IRC_CMDCHAR_LEN         4       /* Prefix for plugin commands (e.g. !). */
+#define IRC_MESSAGE_LEN         512     /* Official length per message. */
+#define IRC_ARGS_MAX            32      /* Own supported number of arguments per message. */
 
 /* Network limits. */
 #define IRC_HOST_LEN            64      /* Hostname length.. */
--- a/lib/irccd/server.c	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/server.c	Sun Jan 24 18:31:50 2021 +0100
@@ -16,19 +16,13 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <sys/types.h>
-#include <sys/socket.h>
 #include <assert.h>
-#include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
 #include <poll.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 
 #include "config.h"
 
@@ -42,85 +36,49 @@
 #include "server.h"
 #include "util.h"
 
+static inline void
+clear_channels(struct irc_server *s, bool free)
+{
+	struct irc_channel *c, *tmp;
+
+	LIST_FOREACH_SAFE(c, &s->channels, link, tmp) {
+		if (free)
+			irc_channel_finish(c);
+		else
+			irc_channel_clear(c);
+	}
+
+	if (free)
+		LIST_INIT(&s->channels);
+}
+
+static inline void
+clear_server(struct irc_server *s)
+{
+	free(s->bufwhois.nickname);
+	free(s->bufwhois.username);
+	free(s->bufwhois.realname);
+	free(s->bufwhois.hostname);
+	free(s->bufwhois.channels);
+
+	memset(&s->params, 0, sizeof (s->params));
+	memset(&s->bufwhois, 0, sizeof (s->bufwhois));
+}
+
+static inline bool
+is_self(const struct irc_server *s, const char *nick)
+{
+	return strncmp(s->ident.nickname, nick, strlen(s->ident.nickname)) == 0;
+}
+
+#if 0
+
 struct origin {
 	char nickname[IRC_NICKNAME_LEN];
 	char username[IRC_USERNAME_LEN];
 	char host[IRC_HOST_LEN];
 };
 
-struct message {
-	char *prefix;
-	char *cmd;
-	char *args[32];
-	char buf[512];
-};
-
-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_UTIL_SIZE(msg->args); ++a) {
-		if (*ptr == ':') {
-			msg->args[a] = ptr + 1;
-			ptr = strchr(ptr, '\0');
-		} else
-			scan(&ptr, &msg->args[a]);
-	}
-
-	if (a >= IRC_UTIL_SIZE(msg->args))
-		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;
-}
-
-#if 0
-
 static const struct origin *
 parse_origin(const char *prefix)
 {
@@ -142,18 +100,10 @@
 static void
 add_nick(const struct irc_server *s, struct irc_channel *ch, const char *nick)
 {
-	char mode = 0, symbol = 0;
+	char mode = 0, prefix = 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;
-			symbol = s->prefixes[i].token;
-			++nick;
-			break;
-		}
-	}
-
-	irc_channel_add(ch, nick, mode, symbol);
+	irc_server_strip(s, &nick, &mode, &prefix);
+	irc_channel_add(ch, nick, mode, prefix);
 }
 
 static struct irc_channel *
@@ -218,16 +168,20 @@
 static void
 read_support_prefix(struct irc_server *s, const char *value)
 {
-	char modes[16 + 1] = {0};
-	char tokens[16 + 1] = {0};
+	char modes[IRC_UTIL_SIZE(s->params.prefixes) + 1] = {0};
+	char tokens[IRC_UTIL_SIZE(s->params.prefixes) + 1] = {0};
+	char fmt[32] = {0};
 
-	if (sscanf(value, "(%16[^)])%16s", modes, tokens) == 2) {
+	snprintf(fmt, sizeof (fmt), "(%%%zu[^)])%%%zus",
+	    sizeof (modes) - 1, sizeof (tokens) - 1);
+
+	if (sscanf(value, fmt, modes, tokens) == 2) {
 		char *pm = modes;
 		char *tk = tokens;
 
-		for (size_t i = 0; i < 16 && *pm && *tk; ++i) {
-			s->prefixes[i].mode = *pm++;
-			s->prefixes[i].token = *tk++;
+		for (size_t i = 0; i < IRC_UTIL_SIZE(s->params.prefixes) && *pm && *tk; ++i) {
+			s->params.prefixes[i].mode = *pm++;
+			s->params.prefixes[i].token = *tk++;
 		}
 	}
 }
@@ -235,11 +189,11 @@
 static void
 read_support_chantypes(struct irc_server *s, const char *value)
 {
-	strlcpy(s->chantypes, value, sizeof (s->chantypes));
+	strlcpy(s->params.chantypes, value, sizeof (s->params.chantypes));
 }
 
 static void
-handle_connect(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_connect(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)msg;
 
@@ -255,7 +209,15 @@
 }
 
 static void
-handle_support(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_disconnect(struct irc_server *s, struct irc_event *ev)
+{
+	s->state = IRC_SERVER_STATE_NONE;
+	ev->type = IRC_EVENT_DISCONNECT;
+	ev->server = s;
+}
+
+static void
+handle_support(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)ev;
 
@@ -274,7 +236,7 @@
 }
 
 static void
-handle_invite(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_invite(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	ev->type = IRC_EVENT_INVITE;
 	ev->invite.origin = strdup(msg->args[0]);
@@ -286,7 +248,7 @@
 }
 
 static void
-handle_join(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_join(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	ev->type = IRC_EVENT_JOIN;
 	ev->join.origin = strdup(msg->prefix);
@@ -296,7 +258,7 @@
 }
 
 static void
-handle_kick(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_kick(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	ev->type = IRC_EVENT_KICK;
 	ev->kick.origin = strdup(msg->prefix);
@@ -321,7 +283,7 @@
 }
 
 static void
-handle_mode(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_mode(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 	(void)ev;
@@ -339,7 +301,7 @@
 }
 
 static void
-handle_part(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_part(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	struct irc_channel *ch;
 
@@ -357,7 +319,7 @@
 }
 
 static void
-handle_msg(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_msg(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 
@@ -380,7 +342,7 @@
 }
 
 static void
-handle_nick(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_nick(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	ev->type = IRC_EVENT_NICK;
 	ev->nick.origin = strdup(msg->prefix);
@@ -388,11 +350,11 @@
 
 	/* Update nickname if it is myself. */
 	if (is_self(s, ev->nick.origin) == 0)
-		strlcpy(s->nickname, ev->nick.nickname, sizeof (s->nickname));
+		strlcpy(s->ident.nickname, ev->nick.nickname, sizeof (s->ident.nickname));
 }
 
 static void
-handle_notice(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_notice(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 
@@ -403,7 +365,7 @@
 }
 
 static void
-handle_topic(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_topic(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 
@@ -414,17 +376,17 @@
 }
 
 static void
-handle_ping(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_ping(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 	(void)ev;
 	(void)msg;
 
-	//irc_server_send(s, "PONG %s", args[1]);
+	irc_server_send(s, "PONG %s", msg->args[1]);
 }
 
 static void
-handle_names(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_names(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 	(void)ev;
@@ -442,7 +404,7 @@
 }
 
 static void
-handle_endofnames(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_endofnames(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 	(void)ev;
@@ -461,8 +423,8 @@
 	fp = open_memstream(&ev->names.names, &length);
 
 	LIST_FOREACH(u, &ch->users, link) {
-		if (u->mode)
-			fprintf(fp, "%c", sym(s, u->mode));
+		if (u->symbol)
+			fprintf(fp, "%c", u->symbol);
 
 		fprintf(fp, "%s", u->nickname);
 
@@ -474,7 +436,7 @@
 }
 
 static void
-handle_whoisuser(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_whoisuser(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)s;
 	(void)ev;
@@ -487,7 +449,7 @@
 }
 
 static void
-handle_whoischannels(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_whoischannels(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)ev;
 
@@ -514,7 +476,7 @@
 }
 
 static void
-handle_endofwhois(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle_endofwhois(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	(void)msg;
 
@@ -526,7 +488,7 @@
 
 static const struct handler {
 	const char *command;
-	void (*handle)(struct irc_server *, struct irc_event *, struct message *);
+	void (*handle)(struct irc_server *, struct irc_event *, struct irc_conn_msg *);
 } handlers[] = {
 	/* Must be kept ordered. */
 	{ "001",        handle_connect          },
@@ -562,7 +524,7 @@
 }
 
 static void
-handle(struct irc_server *s, struct irc_event *ev, struct message *msg)
+handle(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg)
 {
 	const struct handler *h;
 
@@ -576,380 +538,18 @@
 }
 
 static void
-clear(struct irc_server *s)
-{
-	struct irc_channel *ch, *tmp;
-
-	s->state = IRC_SERVER_STATE_DISCONNECTED;
-
-	if (s->fd != 0) {
-		close(s->fd);
-		s->fd = 0;
-	}
-
-	if (s->ai) {
-		freeaddrinfo(s->ai);
-		s->ai = NULL;
-		s->aip = NULL;
-	}
-
-#if defined(IRCCD_WITH_SSL)
-	if (s->ssl) {
-		SSL_free(s->ssl);
-		s->ssl = NULL;
-	}
-	if (s->ctx) {
-		SSL_CTX_free(s->ctx);
-		s->ctx = NULL;
-	}
-#endif
-
-	LIST_FOREACH_SAFE(ch, &s->channels, link, tmp) {
-		irc_channel_finish(ch);
-		free(ch);
-	}
-	LIST_INIT(&s->channels);
-}
-
-static bool
-lookup(struct irc_server *s)
-{
-	struct addrinfo hints = {
-		.ai_socktype = SOCK_STREAM,
-	};
-	char service[16];
-	int ret;
-
-	snprintf(service, sizeof (service), "%hu", s->port);
-
-	if ((ret = getaddrinfo(s->hostname, service, &hints, &s->ai)) != 0)
-		irc_log_warn("server %s: %s", s->name, gai_strerror(ret));
-
-	s->aip = s->ai;
-
-	return true;
-}
-
-static void
 auth(struct irc_server *s)
 {
 	s->state = IRC_SERVER_STATE_CONNECTED;
 
-	if (s->password[0])
-		irc_server_send(s, "PASS %s", s->password);
-
-	irc_server_send(s, "NICK %s", s->nickname);
-	irc_server_send(s, "USER %s %s %s :%s", s->username,
-		s->username, s->username, s->realname);
-}
-
-#if defined(IRCCD_WITH_SSL)
-
-static void
-update(struct irc_server *s, int ret)
-{
-	(void)s;
-	(void)ret;
-
-	assert(s);
-
-	if (!(s->flags & IRC_SERVER_FLAGS_SSL))
-		return;
-
-	switch (SSL_get_error(s->ssl, ret)) {
-	case SSL_ERROR_WANT_READ:
-		s->ssl_state = IRC_SERVER_SSL_NEED_READ;
-		break;
-	case SSL_ERROR_WANT_WRITE:
-		s->ssl_state = IRC_SERVER_SSL_NEED_WRITE;
-		break;
-	case SSL_ERROR_SSL:
-		clear(s);
-		break;
-	default:
-		s->ssl_state = IRC_SERVER_SSL_NONE;
-		break;
-	}
-}
-
-#endif
-
-static void
-handshake(struct irc_server *s)
-{
-	assert(s);
-
-	if (!(s->flags & IRC_SERVER_FLAGS_SSL))
-		auth(s);
-	else {
-#if defined(IRCCD_WITH_SSL)
-		int r;
-
-		s->state = IRC_SERVER_STATE_HANDSHAKING;
-
-		if ((r = SSL_do_handshake(s->ssl)) > 0)
-			auth(s);
-
-		update(s, r);
-#endif
-	}
-}
-
-static void
-try_connect(struct irc_server *s)
-{
-	assert(s);
-
-	if (!(s->flags & IRC_SERVER_FLAGS_SSL))
-		handshake(s);
-	else {
-#if defined(IRCCD_WITH_SSL)
-		if (!s->ctx)
-			s->ctx = SSL_CTX_new(TLS_method());
-		if (!s->ssl) {
-			s->ssl = SSL_new(s->ctx);
-			SSL_set_fd(s->ssl, s->fd);
-		}
-
-		SSL_set_connect_state(s->ssl);
-		handshake(s);
-#endif
-	}
-}
-
-static bool
-set_nonblock(struct irc_server *s)
-{
-	int cflags = 0;
-
-	if ((cflags = fcntl(s->fd, F_GETFL)) < 0 ||
-	    fcntl(s->fd, F_SETFL, cflags | O_NONBLOCK) < 0)
-		return false;
-
-	return true;
-}
-
-static bool
-create(struct irc_server *s)
-{
-	s->fd = socket(s->aip->ai_family, s->aip->ai_socktype,
-	    s->aip->ai_protocol);
-
-	return set_nonblock(s);
-}
-
-static void
-dial(struct irc_server *s)
-{
-	/* No more address available. */
-	if (s->aip == NULL) {
-		clear(s);
-		return;
-	}
-
-	for (; s->aip; s->aip = s->aip->ai_next) {
-		/* We may need to close a socket that was open earlier. */
-		if (s->fd != 0)
-			close(s->fd);
-
-		if (!create(s)) {
-			irc_log_warn("server %s: %s", s->name, strerror(errno));
-			continue;
-		}
-
-		/*
-		 * With some luck, the connection completes immediately,
-		 * otherwise we will need to wait until the socket is writable.
-		 */
-		if (connect(s->fd, s->aip->ai_addr, s->aip->ai_addrlen) == 0) {
-			try_connect(s);
-			break;
-		}
-
-		/* Connect failed, check why. */
-		switch (errno) {
-		case EINPROGRESS:
-		case EAGAIN:
-			/* Let the writable state to determine. */
-			return;
-		default:
-			irc_log_warn("server %s: %s", s->name, strerror(errno));
-			break;
-		}
-	}
-}
-
-static size_t
-input_ssl(struct irc_server *s, char *dst, size_t dstsz)
-{
-	int nr;
-
-	if ((nr = SSL_read(s->ssl, dst, dstsz)) <= 0) {
-		update(s, nr);
-		return 0;
-	}
-
-	s->ssl_state = IRC_SERVER_SSL_NONE;
+	if (s->ident.password[0])
+		irc_server_send(s, "PASS %s", s->ident.password);
 
-	return nr;
-}
-
-static size_t
-input_clear(struct irc_server *s, char *buf, size_t bufsz)
-{
-	ssize_t nr;
-
-	if ((nr = recv(s->fd, buf, bufsz, 0)) <= 0) {
-		clear(s);
-		return 0;
-	}
-
-	return nr;
-}
-
-static void
-input(struct irc_server *s)
-{
-	size_t len = strlen(s->in);
-	size_t cap = sizeof (s->in) - len - 1;
-	size_t nr = 0;
-
-	if (s->flags & IRC_SERVER_FLAGS_SSL) {
-#if defined(IRCCD_WITH_SSL)
-		nr = input_ssl(s, s->in + len, cap);
-#endif
-	} else
-		nr = input_clear(s, s->in + len, cap);
-
-	if (nr > 0)
-		s->in[len + nr] = '\0';
-}
-
-static void
-output(struct irc_server *s)
-{
-	ssize_t ns = 0;
-
-	if (s->flags & IRC_SERVER_FLAGS_SSL) {
-#if defined(IRCCD_WITH_SSL)
-		ns = SSL_write(s->ssl, s->out, strlen(s->out));
-		update(s, ns);
-#endif
-	} else if ((ns = send(s->fd, s->out, strlen(s->out), 0)) <= 0)
-		clear(s);
-
-	if (ns > 0) {
-		/* Optimize if everything was sent. */
-		if ((size_t)ns >= sizeof (s->out))
-			s->out[0] = '\0';
-		else
-			memmove(s->out, s->out + ns, sizeof (s->out) - ns);
-	}
-}
-
-static void
-prepare_connecting(const struct irc_server *s, struct pollfd *pfd)
-{
-	(void)s;
-
-#if defined(IRCCD_WITH_SSL)
-	if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl && s->ctx) {
-		switch (s->ssl_state) {
-		case IRC_SERVER_SSL_NEED_READ:
-			pfd->events |= POLLIN;
-			break;
-		case IRC_SERVER_SSL_NEED_WRITE:
-			pfd->events |= POLLOUT;
-			break;
-		default:
-			break;
-		}
-	} else
-#endif
-		pfd->events |= POLLOUT;
+	irc_server_send(s, "USER %s %s %s :%s", s->ident.username,
+	    s->ident.username, s->ident.username, s->ident.realname);
+	irc_server_send(s, "NICK %s", s->ident.nickname);
 }
 
-static void
-prepare_ready(const struct irc_server *s, struct pollfd *pfd)
-{
-#if defined(IRCCD_WITH_SSL)
-	if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl_state) {
-		switch (s->ssl_state) {
-		case IRC_SERVER_SSL_NEED_READ:
-			pfd->events |= POLLIN;
-			break;
-		case IRC_SERVER_SSL_NEED_WRITE:
-			pfd->events |= POLLOUT;
-			break;
-		default:
-			break;
-		}
-	} else {
-#endif
-		pfd->events |= POLLIN;
-
-		if (s->out[0])
-			pfd->events |= POLLOUT;
-#if defined(IRCCD_WITH_SSL)
-	}
-#endif
-}
-
-static void
-flush_connecting(struct irc_server *s, const struct pollfd *pfd)
-{
-	(void)pfd;
-
-	int res, err = -1;
-	socklen_t len = sizeof (int);
-
-	if ((res = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &err, &len)) < 0 || err) {
-		irc_log_warn("server %s: %s", s->name, strerror(res ? err : errno));
-		dial(s);
-	} else
-		try_connect(s);
-}
-
-static void
-flush_handshaking(struct irc_server *s, const struct pollfd *pfd)
-{
-	(void)pfd;
-
-	handshake(s);
-}
-
-static void
-flush_ready(struct irc_server *s, const struct pollfd *pfd)
-{
-	if (pfd->revents & POLLERR || pfd->revents & POLLHUP) {
-		clear(s);
-		return;
-	}
-
-	if (pfd->revents & POLLIN)
-		input(s);
-	if (pfd->revents & POLLOUT)
-		output(s);
-}
-
-static const struct {
-	void (*prepare)(const struct irc_server *, struct pollfd *);
-	void (*flush)(struct irc_server *, const struct pollfd *);
-} io_table[] = {
-	[IRC_SERVER_STATE_CONNECTING] = {
-		prepare_connecting,
-		flush_connecting
-	},
-	[IRC_SERVER_STATE_HANDSHAKING] = {
-		prepare_ready,
-		flush_handshaking
-	},
-	[IRC_SERVER_STATE_CONNECTED] = {
-		prepare_ready,
-		flush_ready
-	},
-};
-
 struct irc_server *
 irc_server_new(const char *name,
                const char *nickname,
@@ -967,14 +567,18 @@
 	struct irc_server *s;
 
 	s = irc_util_calloc(1, sizeof (*s));
-	s->port = port;
+
+	/* Connection. */
+	s->conn.port = port;
+	strlcpy(s->conn.hostname, hostname, sizeof (s->conn.hostname));
 
+	/* Identity. */
+	strlcpy(s->ident.nickname, nickname, sizeof (s->ident.nickname));
+	strlcpy(s->ident.username, username, sizeof (s->ident.username));
+	strlcpy(s->ident.realname, realname, sizeof (s->ident.realname));
+
+	/* Server itslf. */
 	strlcpy(s->name, name, sizeof (s->name));
-	strlcpy(s->nickname, nickname, sizeof (s->nickname));
-	strlcpy(s->username, username, sizeof (s->username));
-	strlcpy(s->realname, realname, sizeof (s->realname));
-	strlcpy(s->hostname, hostname, sizeof (s->hostname));
-
 	LIST_INIT(&s->channels);
 
 	return s;
@@ -985,12 +589,10 @@
 {
 	assert(s);
 
-	s->state = IRC_SERVER_STATE_CONNECTING;
-
-	if (!lookup(s))
-		clear(s);
+	if (irc_conn_connect(&s->conn))
+		s->state = IRC_SERVER_STATE_CONNECTING;
 	else
-		dial(s);
+		s->state = IRC_SERVER_STATE_DISCONNECTED;
 }
 
 void
@@ -998,27 +600,45 @@
 {
 	assert(s);
 
-	clear(s);
+	s->state = IRC_SERVER_STATE_DISCONNECTED;
+
+	irc_conn_disconnect(&s->conn);
+
+	clear_channels(s, false);
+	clear_server(s);
 }
 
 void
 irc_server_prepare(const struct irc_server *s, struct pollfd *pfd)
 {
-	pfd->fd = s->fd;
-	pfd->events = 0;
+	assert(s);
+	assert(pfd);
 
-	if (io_table[s->state].prepare)
-		io_table[s->state].prepare(s, pfd);
+	irc_conn_prepare(&s->conn, pfd);
 }
 
 void
 irc_server_flush(struct irc_server *s, const struct pollfd *pfd)
 {
-	if (pfd->fd != s->fd)
+	assert(s);
+	assert(pfd);
+
+	if (!irc_conn_flush(&s->conn, pfd))
+		return irc_server_disconnect(s);
+	if (s->conn.state != IRC_CONN_STATE_READY)
 		return;
 
-	if (io_table[s->state].flush)
-		io_table[s->state].flush(s, pfd);
+	switch (s->state) {
+	case IRC_SERVER_STATE_CONNECTING:
+		/*
+		 * Now the conn object is ready which means the server has
+		 * to authenticate.
+		 */
+		auth(s);
+		break;
+	default:
+		break;
+	}
 }
 
 bool
@@ -1027,23 +647,14 @@
 	assert(s);
 	assert(ev);
 
-	struct message msg;
-	char *pos;
-	size_t length;
-
-	if (!(pos = strstr(s->in, "\r\n")))
-		return false;
+	struct irc_conn_msg msg = {0};
 
-	/* Turn end of the string at delimiter. */
-	*pos = 0;
-	length = pos - s->in;
+	if (irc_conn_poll(&s->conn, &msg))
+		return handle(s, ev, &msg), true;
+	if (s->state == IRC_SERVER_STATE_DISCONNECTED)
+		return handle_disconnect(s, ev), true;
 
-	if (length > 0 && parse(&msg, s->in))
-		handle(s, ev, &msg);
-
-	memmove(s->in, pos + 2, sizeof (s->in) - (length + 2));
-
-	return true;
+	return false;
 }
 
 struct irc_channel *
@@ -1069,23 +680,12 @@
 
 	char buf[IRC_BUF_LEN];
 	va_list ap;
-	size_t len, avail, required;
 
 	va_start(ap, fmt);
-	required = vsnprintf(buf, sizeof (buf), fmt, ap);
+	vsnprintf(buf, sizeof (buf), fmt, ap);
 	va_end(ap);
 
-	len = strlen(s->out);
-	avail = sizeof (s->out) - len;
-
-	/* Don't forget \r\n. */
-	if (required + 2 >= avail)
-		return false;
-
-	strlcat(s->out, buf, sizeof (s->out));
-	strlcat(s->out, "\r\n", sizeof (s->out));
-
-	return true;
+	return irc_conn_send(&s->conn, buf);
 }
 
 bool
@@ -1218,8 +818,8 @@
 	assert(s);
 	assert(nick);
 
-	if (s->state == IRC_SERVER_STATE_DISCONNECTED) {
-		strlcpy(s->nickname, nick, sizeof (s->nickname));
+	if (s->state <= IRC_SERVER_STATE_DISCONNECTED) {
+		strlcpy(s->ident.nickname, nick, sizeof (s->ident.nickname));
 		return true;
 	}
 
@@ -1242,34 +842,28 @@
 	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)
+void
+irc_server_strip(const struct irc_server *s, const char **nick, char *mode, char *prefix)
 {
 	assert(s);
-	assert(item);
-
-	struct irc_server_namemode ret = {0};
+	assert(*nick);
 
-	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;
+	if (mode)
+		*mode = 0;
+	if (prefix)
+		*mode = 0;
+
+	for (size_t i = 0; i < IRC_UTIL_SIZE(s->params.prefixes); ++i) {
+		if (**nick == s->params.prefixes[i].token) {
+			*mode = s->params.prefixes[i].mode;
+			*prefix = s->params.prefixes[i].token;
+			*nick += 1;
 			break;
 		}
 	}
-
-	ret.name = (char *)item;
-
-	return ret;
 }
 
 void
@@ -1287,7 +881,7 @@
 	assert(s->refc >= 1);
 
 	if (--s->refc == 0) {
-		clear(s);
+		clear_channels(s, true);
 		free(s);
 	}
 }
--- a/lib/irccd/server.h	Thu Jan 21 23:15:20 2021 +0100
+++ b/lib/irccd/server.h	Sun Jan 24 18:31:50 2021 +0100
@@ -23,12 +23,8 @@
 #include <stdbool.h>
 #include <stddef.h>
 
-#include "config.h"
-
-#if defined(IRCCD_WITH_SSL)
-#       include <openssl/ssl.h>
-#endif
-
+#include "channel.h"
+#include "conn.h"
 #include "event.h"
 #include "limits.h"
 
@@ -37,9 +33,9 @@
 struct irc_channel;
 
 enum irc_server_state {
+	IRC_SERVER_STATE_NONE,
 	IRC_SERVER_STATE_DISCONNECTED,
 	IRC_SERVER_STATE_CONNECTING,
-	IRC_SERVER_STATE_HANDSHAKING,
 	IRC_SERVER_STATE_CONNECTED,
 	IRC_SERVER_STATE_WAITING,
 	IRC_SERVER_STATE_NUM
@@ -53,70 +49,33 @@
 	IRC_SERVER_FLAGS_IPV6          = (1 << 4)
 };
 
-struct irc_server_prefix {
-	char mode;
-	char token;
+struct irc_server_ident {
+	char nickname[IRC_NICKNAME_LEN];
+	char username[IRC_USERNAME_LEN];
+	char realname[IRC_REALNAME_LEN];
+	char password[IRC_PASSWORD_LEN];
+	char ctcpversion[IRC_CTCPVERSION_LEN];
 };
 
-#if defined(IRCCD_WITH_SSL)
-
-enum irc_server_ssl_state {
-	IRC_SERVER_SSL_NONE,
-	IRC_SERVER_SSL_NEED_READ,
-	IRC_SERVER_SSL_NEED_WRITE,
-};
-
-#endif
-
-struct irc_server_namemode {
-	char mode;
-	char sym;
-	char *name;
+struct irc_server_params {
+	char chantypes[IRC_CHANTYPES_LEN];
+	struct {
+		char mode;              /* Mode (e.g. ov). */
+		char token;             /* Symbol used (e.g. @+). */
+	} prefixes[IRC_USERMODES_LEN];
 };
 
 struct irc_server {
-	/* Connection settings. */
 	char name[IRC_ID_LEN];
-	char hostname[IRC_HOST_LEN];
-	char password[IRC_PASSWORD_LEN];
-	unsigned short port;
-	enum irc_server_flags flags;
-
-	/* Plugin prefix. */
 	char commandchar[IRC_CMDCHAR_LEN];
-
-	/* IRC identity. */
-	char nickname[IRC_NICKNAME_LEN];
-	char username[IRC_USERNAME_LEN];
-	char realname[IRC_REALNAME_LEN];
-	char ctcpversion[IRC_CTCPVERSION_LEN];
-
-	LIST_HEAD(, irc_channel) channels;
-
-	/* Network connectivity. */
-	int fd;
-	struct addrinfo *ai;
-	struct addrinfo *aip;
-	char in[IRC_BUF_LEN];
-	char out[IRC_BUF_LEN];
+	struct irc_server_ident ident;
+	struct irc_server_params params;
 	enum irc_server_state state;
-
-	/* OpenSSL support. */
-#if defined(IRCCD_WITH_SSL)
-	SSL_CTX *ctx;
-	SSL *ssl;
-	enum irc_server_ssl_state ssl_state;
-#endif
-
+	enum irc_server_flags flags;
+	struct irc_channel_list channels;
 	struct irc_event_whois bufwhois;
-
-	/* Reference count. */
+	struct irc_conn conn;
 	size_t refc;
-
-	/* IRC server settings. */
-	char chantypes[8];
-	struct irc_server_prefix prefixes[16];
-
 	LIST_ENTRY(irc_server) link;
 };
 
@@ -192,8 +151,8 @@
 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_strip(const struct irc_server *, const char **, char *, char *);
 
 void
 irc_server_incref(struct irc_server *);