changeset 940:94cae3129870

irccd: add begin of transports
author David Demelier <markand@malikania.fr>
date Thu, 14 Jan 2021 10:46:41 +0100
parents a62c56c8b5ca
children e43ccb1f0ace
files .hgignore Makefile config.mk irccd/main.c lib/irccd/config.h.in lib/irccd/peer.c lib/irccd/peer.h lib/irccd/plugin.c lib/irccd/server.c lib/irccd/transport.c lib/irccd/transport.h
diffstat 11 files changed, 687 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed Jan 13 17:18:35 2021 +0100
+++ b/.hgignore	Thu Jan 14 10:46:41 2021 +0100
@@ -21,6 +21,7 @@
 irccd/irccd
 
 # temporary files.
+^lib/irccd/config\.h$
 \.a$
 \.o$
 \.d$
--- a/Makefile	Wed Jan 13 17:18:35 2021 +0100
+++ b/Makefile	Thu Jan 14 10:46:41 2021 +0100
@@ -21,6 +21,10 @@
 
 include config.mk
 
+MAJOR=                  4
+MINOR=                  0
+PATCH=                  0
+
 IRCCD=                  irccd/irccd
 IRCCD_SRCS=             irccd/main.c
 IRCCD_OBJS=             ${IRCCD_SRCS:.c=.o}
@@ -36,9 +40,11 @@
 LIBIRCCD_SRCS=          lib/irccd/dl-plugin.c
 LIBIRCCD_SRCS+=         lib/irccd/irccd.c
 LIBIRCCD_SRCS+=         lib/irccd/log.c
+LIBIRCCD_SRCS+=         lib/irccd/peer.c
 LIBIRCCD_SRCS+=         lib/irccd/plugin.c
 LIBIRCCD_SRCS+=         lib/irccd/server.c
 LIBIRCCD_SRCS+=         lib/irccd/subst.c
+LIBIRCCD_SRCS+=         lib/irccd/transport.c
 LIBIRCCD_SRCS+=         lib/irccd/util.c
 
 ifeq (${WITH_JS},yes)
@@ -86,6 +92,10 @@
 endif
 LIBS+=                  -l irccd
 
+ifeq (${WITH_SSL},yes)
+LIBS+=                  -l ssl -l crypto
+endif
+
 all: ${IRCCD}
 
 .c.o:
@@ -102,7 +112,21 @@
 	${MAKE} CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" -C extern/libduktape
 endif
 
-${LIBIRCCD_OBJS}: ${LIBCOMPAT}
+ifneq (${WITH_JS},yes)
+EXTRA_SEDS+=    -e "/IRCCD_WITH_JS/d"
+endif
+
+ifneq (${WITH_SSL},yes)
+EXTRA_SEDS+=    -e "/IRCCD_WITH_SSL/d"
+endif
+
+lib/irccd/config.h: lib/irccd/config.h.in Makefile config.mk
+	sed -e "s/@IRCCD_VERSION_MAJOR@/${MAJOR}/" \
+	    -e "s/@IRCCD_VERSION_MINOR@/${MINOR}/" \
+	    -e "s/@IRCCD_VERSION_PATCH@/${PATCH}/" \
+	    ${EXTRA_SEDS} < $< > $@
+
+${LIBIRCCD_OBJS}: ${LIBCOMPAT} lib/irccd/config.h
 
 ${LIBIRCCD}: ${LIBIRCCD_OBJS}
 	${CMD.ar}
--- a/config.mk	Wed Jan 13 17:18:35 2021 +0100
+++ b/config.mk	Thu Jan 14 10:46:41 2021 +0100
@@ -29,6 +29,7 @@
 
 # User options.
 WITH_JS=                yes
+WITH_SSL=               no
 
 # System dependant macros.
 OS:=                    $(shell uname -s)
--- a/irccd/main.c	Wed Jan 13 17:18:35 2021 +0100
+++ b/irccd/main.c	Thu Jan 14 10:46:41 2021 +0100
@@ -25,6 +25,7 @@
 #include <irccd/plugin.h>
 #include <irccd/log.h>
 #include <irccd/server.h>
+#include <irccd/transport.h>
 
 static struct irc_plugin js = {
 	.name = "example"
@@ -53,11 +54,13 @@
 	irc_init();
 	irc_log_set_verbose(true);
 	irc_server_join(&s, "#test", NULL);
-	//irc_server_join(&freenode, "#irccd", NULL);
+	irc_transport_bind("/tmp/irccd.sock");
 	irc_add_server(&s);
 	irc_add_server(&freenode);
+#if 0
 	if (!irc_js_plugin_open(&js, "/Users/markand/Dev/irccd-4/test.js"))
 		return 1;
 	irc_add_plugin(&js);
+#endif
 	irc_run();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/config.h.in	Thu Jan 14 10:46:41 2021 +0100
@@ -0,0 +1,11 @@
+#ifndef IRCCD_CONFIG_H
+#define IRCCD_CONFIG_H
+
+#define IRCCD_VERSION_MAJOR @IRCCD_VERSION_MAJOR@
+#define IRCCD_VERSION_MINOR @IRCCD_VERSION_MINOR@
+#define IRCCD_VERSION_PATCH @IRCCD_VERSION_PATCH@
+
+#define IRCCD_WITH_JS
+#define IRCCD_WITH_SSL
+
+#endif /* !IRCCD_CONFIG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/peer.c	Thu Jan 14 10:46:41 2021 +0100
@@ -0,0 +1,421 @@
+/*
+ * peer.c -- client connected to irccd
+ *
+ * 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 <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <assert.h>
+
+#include "irccd.h"
+#include "log.h"
+#include "peer.h"
+#include "server.h"
+#include "util.h"
+
+static size_t
+parse(char *line, const char **args, size_t max)
+{
+	size_t idx;
+
+	/* Skip command. */
+	while (*line && !isspace(*line++))
+		continue;
+
+	if (!*line)
+		return 0;
+
+	for (idx = 0; idx < max; ++idx) {
+		char *sp = strchr(line, ' ');
+
+		if (!sp || idx + 1 >= max) {
+			args[idx++] = line;
+			break;
+		}
+
+		*sp = '\0';
+		args[idx] = line;
+		line = sp + 1;
+	}
+
+	return idx;
+}
+
+static struct irc_server *
+require_server(struct irc_peer *p, const char *id)
+{
+	struct irc_server *s;
+
+	if (!(s = irc_find_server(id))) {
+		irc_peer_send(p, "server %s not found", id);
+		return NULL;
+	}
+
+	return s;
+}
+
+/*
+ * MESSAGE server channel message
+ */
+static int
+cmd_message(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) != 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_message(s, args[1], args[2]);
+
+	return 0;
+}
+
+/*
+ * ME server channel message
+ */
+static int
+cmd_me(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) != 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_me(s, args[1], args[2]);
+
+	return 0;
+}
+
+/*
+ * MODE server channel mode [limit] [user] [mask]
+ */
+static int
+cmd_mode(struct irc_peer *p, char *line)
+{
+	const char *args[6] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 6) < 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_mode(s, args[1], args[2],
+	    args[3][0] ? args[3] : NULL,
+	    args[4][0] ? args[4] : NULL,
+	    args[5][0] ? args[5] : NULL
+	);
+
+	return 0;
+}
+
+/*
+ * NOTICE server channel message
+ */
+static int
+cmd_notice(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) != 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_notice(s, args[1], args[2]);
+
+	return 0;
+}
+
+/*
+ * INVITE server channel target
+ */
+static int
+cmd_invite(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) != 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_invite(s, args[1], args[2]);
+
+	return 0;
+}
+
+/*
+ * JOIN server channel [password]
+ */
+static int
+cmd_join(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) < 2)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_join(s, args[1], args[2][0] ? args[2] : NULL);
+
+	return 0;
+}
+
+/*
+ * KICK server channel target [reason]
+ */
+static int
+cmd_kick(struct irc_peer *p, char *line)
+{
+	const char *args[4] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 4) < 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_kick(s, args[1], args[2], args[3][0] ? args[3] : NULL);
+
+	return 0;
+}
+
+/*
+ * PART server channel [reason]
+ */
+static int
+cmd_part(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) < 2)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return -10;
+
+	irc_server_part(s, args[1], args[2][0] ? args[2] : NULL);
+
+	return 0;
+}
+
+/*
+ * TOPIC server channel topic
+ */
+static int
+cmd_topic(struct irc_peer *p, char *line)
+{
+	const char *args[3] = {0};
+	struct irc_server *s;
+
+	if (parse(line, args, 3) != 3)
+		return EINVAL;
+	if (!(s = require_server(p, args[0])))
+		return 0;
+
+	irc_server_topic(s, args[1], args[2]);
+
+	return 0;
+}
+
+static const struct cmd {
+	const char *name;
+	int (*call)(struct irc_peer *, char *);
+} cmds[] = {
+	{ "INVITE",     cmd_invite      },
+	{ "JOIN",       cmd_join        },
+	{ "KICK",       cmd_kick        },
+	{ "ME",         cmd_me          },
+	{ "MESSAGE",    cmd_message     },
+	{ "MODE",       cmd_mode        },
+	{ "NOTICE",     cmd_notice      },
+	{ "PART",       cmd_part        },
+	{ "TOPIC",      cmd_topic       },
+};
+
+static int
+cmp_cmd(const void *d1, const void *d2)
+{
+	const char *key = d1;
+	const struct cmd *cmd = d2;
+
+	return strncmp(cmd->name, key, strlen(cmd->name));
+}
+
+static const struct cmd *
+find(const char *line)
+{
+	return bsearch(line, cmds, IRC_UTIL_SIZE(cmds), sizeof (struct cmd), cmp_cmd);
+}
+
+static void
+invoke(struct irc_peer *p, char *line)
+{
+	const struct cmd *c = find(line);
+	int er;
+
+	if (!c)
+		irc_peer_send(p, "command not found");
+	else if ((er = c->call(p, line)) != 0 && er != -1)
+		irc_peer_send(p, "%s", strerror(errno));
+	else
+		irc_peer_send(p, "OK");
+}
+
+static void
+dispatch(struct irc_peer *p)
+{
+	char *pos;
+	size_t length;
+
+	while ((pos = strstr(p->in, "\n"))) {
+		/* Turn end of the string at delimiter. */
+		*pos = 0;
+		length = pos - p->in;
+
+		if (length > 0)
+			invoke(p, p->in);
+
+		memmove(p->in, pos + 1, sizeof (p->in) - (length + 1));
+	}
+}
+
+static bool
+input(struct irc_peer *p)
+{
+	char buf[BUFSIZ + 1];
+	ssize_t nr;
+
+	if ((nr = recv(p->fd, buf, BUFSIZ, 0)) <= 0) {
+		irc_log_info("transport: client disconnect");
+		return false;
+	}
+
+	buf[nr] = '\0';
+
+	if (strlcat(p->in, buf, sizeof (p->in)) >= sizeof (p->in)) {
+		errno = EMSGSIZE;
+		return false;
+	}
+
+	dispatch(p);
+
+	return true;
+}
+
+static bool
+output(struct irc_peer *p)
+{
+	ssize_t ns;
+	size_t len = strlen(p->out);
+
+	if ((ns = send(p->fd, p->out, len, 0)) < 0)
+		return false;
+
+	if (ns >= len)
+		memset(p->out, 0, sizeof (p->out));
+	else
+		memmove(p->out, p->out + ns, sizeof (p->out) - ns);
+
+	return true;
+}
+
+bool
+irc_peer_send(struct irc_peer *p, const char *fmt, ...)
+{
+	assert(p);
+	assert(fmt);
+
+	char buf[IRC_BUF_MAX];
+	va_list ap;
+	size_t len, avail, required;
+
+	va_start(ap, fmt);
+	required = vsnprintf(buf, sizeof (buf), fmt, ap);
+	va_end(ap);
+
+	len = strlen(p->out);
+	avail = sizeof (p->out) - len;
+
+	/* Don't forget \n. */
+	if (required + 1 >= avail)
+		return false;
+
+	strlcat(p->out, buf, sizeof (p->out));
+	strlcat(p->out, "\n", sizeof (p->out));
+
+	return true;
+}
+
+void
+irc_peer_prepare(struct irc_peer *p, struct pollfd *fd)
+{
+	assert(p);
+	assert(fd);
+
+	fd->fd = p->fd;;
+	fd->events = POLLIN;
+
+	if (p->out[0])
+		fd->events |= POLLOUT;
+}
+
+bool
+irc_peer_flush(struct irc_peer *p, const struct pollfd *fd)
+{
+	assert(p);
+	assert(fd);
+
+	char buf[IRC_BUF_MAX];
+
+	if (fd->fd != p->fd)
+		return true;
+
+	if (fd->revents & POLLIN && !input(p))
+		return false;
+	if (fd->revents & POLLOUT && !output(p))
+		return false;
+
+	return true;
+}
+
+void
+irc_peer_finish(struct irc_peer *p)
+{
+	assert(p);
+
+	close(p->fd);
+	memset(p, 0, sizeof (*p));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/peer.h	Thu Jan 14 10:46:41 2021 +0100
@@ -0,0 +1,46 @@
+/*
+ * peer.h -- client connected to irccd
+ *
+ * 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_PEER_H
+#define IRCCD_PEER_H
+
+#include <stdbool.h>
+
+#include "limits.h"
+
+struct pollfd;
+
+struct irc_peer {
+	int fd;
+	char in[IRC_BUF_MAX];
+	char out[IRC_BUF_MAX];
+};
+
+bool
+irc_peer_send(struct irc_peer *, const char *, ...);
+
+void
+irc_peer_prepare(struct irc_peer *, struct pollfd *);
+
+bool
+irc_peer_flush(struct irc_peer *, const struct pollfd *);
+
+void
+irc_peer_finish(struct irc_peer *);
+
+#endif /* !IRCCD_PEER_H */
--- a/lib/irccd/plugin.c	Wed Jan 13 17:18:35 2021 +0100
+++ b/lib/irccd/plugin.c	Thu Jan 14 10:46:41 2021 +0100
@@ -18,6 +18,7 @@
 
 #include <assert.h>
 #include <stddef.h>
+#include <string.h>
 
 #include "plugin.h"
 
@@ -167,4 +168,6 @@
 
 	if (plg->finish)
 		plg->finish(plg);
+
+	memset(plg, 0, sizeof (*plg));
 }
--- a/lib/irccd/server.c	Wed Jan 13 17:18:35 2021 +0100
+++ b/lib/irccd/server.c	Thu Jan 14 10:46:41 2021 +0100
@@ -437,7 +437,6 @@
 static void
 clear(struct irc_server *s)
 {
-	puts("clear...");
 	s->state = IRC_SERVER_STATE_DISCONNECTED;
 
 	if (s->fd != 0) {
@@ -784,7 +783,6 @@
 {
 	assert(s);
 
-	puts("HERE?!");
 	clear(s);
 }
 
@@ -855,7 +853,7 @@
 	assert(s);
 	assert(fmt);
 
-	char buf[sizeof (s->out)];
+	char buf[IRC_BUF_MAX];
 	va_list ap;
 	size_t len, avail, required;
 
@@ -877,11 +875,11 @@
 }
 
 bool
-irc_server_invite(struct irc_server *s, const char *target, const char *channel)
+irc_server_invite(struct irc_server *s, const char *channel, const char *target)
 {
 	assert(s);
+	assert(channel);
 	assert(target);
-	assert(channel);
 
 	return irc_server_send(s, "INVITE %s %s", target, channel);
 }
@@ -914,11 +912,11 @@
 }
 
 bool
-irc_server_kick(struct irc_server *s, const char *target, const char *channel, const char *reason)
+irc_server_kick(struct irc_server *s, const char *channel, const char *target, const char *reason)
 {
 	assert(s);
+	assert(channel);
 	assert(target);
-	assert(channel);
 
 	bool ret;
 
@@ -931,29 +929,29 @@
 }
 
 bool
-irc_server_part(struct irc_server *s, const char *name, const char *reason)
+irc_server_part(struct irc_server *s, const char *channel, const char *reason)
 {
 	assert(s);
-	assert(name);
+	assert(channel);
 
 	bool ret;
 
 	if (reason && strlen(reason) > 0)
-		ret = irc_server_send(s, "PART %s :%s", name, reason);
+		ret = irc_server_send(s, "PART %s :%s", channel, reason);
 	else
-		ret = irc_server_send(s, "PART %s", name);
+		ret = irc_server_send(s, "PART %s", channel);
 
 	return ret;
 }
 
 bool
-irc_server_topic(struct irc_server *s, const char *name, const char *topic)
+irc_server_topic(struct irc_server *s, const char *channel, const char *topic)
 {
 	assert(s);
-	assert(name);
+	assert(channel);
 	assert(topic);
 
-	return irc_server_send(s, "TOPIC %s :%s", name, topic);
+	return irc_server_send(s, "TOPIC %s :%s", channel, topic);
 }
 
 bool
@@ -995,13 +993,13 @@
 }
 
 bool
-irc_server_notice(struct irc_server *s, const char *target, const char *message)
+irc_server_notice(struct irc_server *s, const char *channel, const char *message)
 {
 	assert(s);
-	assert(target);
+	assert(channel);
 	assert(message);
 
-	return irc_server_send(s, "NOTICE %s: %s", target, message);
+	return irc_server_send(s, "NOTICE %s: %s", channel, message);
 }
 
 void
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/transport.c	Thu Jan 14 10:46:41 2021 +0100
@@ -0,0 +1,118 @@
+/*
+ * transport.c -- remote command support
+ *
+ * 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 <sys/types.h>
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "log.h"
+#include "transport.h"
+#include "peer.h"
+
+static struct sockaddr_un addr;
+static int fd = -1;
+
+bool
+irc_transport_bind(const char *path)
+{
+	assert(path);
+
+	/* Silently remove the file first. */
+	if (strlcpy(addr.sun_path, path, sizeof (addr.sun_path)) >= sizeof (addr.sun_path)) {
+		errno = ENAMETOOLONG;
+		goto err;
+	}
+
+	unlink(addr.sun_path);
+
+	if ((fd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0)
+		goto err;
+	if (bind(fd, (const struct sockaddr *)&addr, sizeof (addr)) < 0)
+		goto err;
+	if (listen(fd, 16) < 0)
+		goto err;
+
+	irc_log_info("transport: listening on %s", path);
+	irc_log_debug("transport: file descriptor %d", fd);
+
+	return true;
+
+err:
+	irc_log_warn("transport: %s: %s", path, strerror(errno));
+
+	if (fd != -1) {
+		close(fd);
+		fd = -1;
+	}
+
+	return -1;
+}
+
+void
+irc_transport_prepare(struct pollfd *pfd)
+{
+	assert(pfd);
+
+	if (fd < 0)
+		return;
+
+	pfd->fd = fd;
+	pfd->events = POLLIN;
+}
+
+bool
+irc_transport_flush(const struct pollfd *pfd, struct irc_peer *peer)
+{
+	assert(pfd);
+	assert(peer);
+
+	if (fd < 0 || pfd->fd != fd || !(pfd->revents & POLLIN))
+		return false;
+
+	memset(peer, 0, sizeof (*peer));
+
+	if ((peer->fd = accept(fd, NULL, NULL)) < 0) {
+		irc_log_warn("transport: %s", strerror(errno));
+		return false;
+	}
+
+	irc_log_info("transport: new client connected");
+	irc_peer_send(peer, "IRCCD %d.%d.%d", IRCCD_VERSION_MAJOR,
+	    IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH);
+
+	return true;
+}
+
+void
+irc_transport_finish(void)
+{
+	/* Connection socket. */
+	if (fd != -1)
+		close(fd);
+
+	unlink(addr.sun_path);
+	memset(&addr, 0, sizeof (addr));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/transport.h	Thu Jan 14 10:46:41 2021 +0100
@@ -0,0 +1,42 @@
+/*
+ * transport.h -- remote command support
+ *
+ * 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_TRANSPORT_H
+#define IRCCD_TRANSPORT_H
+
+#include <stdbool.h>
+
+#include "limits.h"
+
+struct pollfd;
+
+struct irc_peer;
+
+bool
+irc_transport_bind(const char *);
+
+void
+irc_transport_prepare(struct pollfd *);
+
+bool
+irc_transport_flush(const struct pollfd *, struct irc_peer *);
+
+void
+irc_transport_finish(void);
+
+#endif /* !IRCCD_TRANSPORT_H */