about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--channel.c80
-rw-r--r--handle.c150
-rw-r--r--ircd.h32
-rw-r--r--loop.c74
-rw-r--r--reply.c209
-rw-r--r--user.c10
-rw-r--r--writef.c39
8 files changed, 507 insertions, 90 deletions
diff --git a/Makefile b/Makefile
index 5953be1..59070a7 100644
--- a/Makefile
+++ b/Makefile
@@ -2,14 +2,17 @@ NAME ?= ircd
 
 CFLAGS += -Wall -Wextra
 
+OBJS += channel.o
 OBJS += handle.o
 OBJS += loop.o
 OBJS += main.o
 OBJS += message.o
 OBJS += peer.o
+OBJS += reply.o
 OBJS += user.o
 OBJS += writef.o
 
+all: ${NAME}
 ${NAME}: ${OBJS}
 	${CC} -o ${NAME} ${CFLAGS} ${LDFLAGS} ${OBJS}
 clean:
diff --git a/channel.c b/channel.c
new file mode 100644
index 0000000..0cf0b30
--- /dev/null
+++ b/channel.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org>
+ *
+ * Permission to use, copy, modify, and 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 "ircd.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <err.h>
+
+
+struct Channel channels[CHANNELS_MAX];
+size_t channels_c;
+
+int
+channel_join(struct Channel *channel, struct Peer *peer)
+{
+	if (channel->users_c == lengthof(channel->users)
+	 || peer->channels_c == lengthof(peer->channels))
+		return 1;
+
+	peer->channels[peer->channels_c++] = channel;
+	channel->users[channel->users_c++] = peer;
+	writechanf(
+		0,
+		channel,
+		":%s!%s@%s JOIN %s",
+		getnick(peer),
+		peer->user,
+		peer->host,
+		channel->name
+	);
+	return 0;
+}
+
+int
+channel_exit(struct Channel *channel, struct Peer *peer)
+{
+	size_t i;
+
+	for (i = 0; i < channel->users_c; ++i)
+		if (channel->users[i]->fd == peer->fd)
+			break;
+	ensure(i != channel->users_c, (void)0, 1);
+	channel->users[i] = channel->users[--channel->users_c];
+
+	for (i = 0; i < peer->channels_c; ++i)
+		if (!strcmp(peer->channels[i]->name, channel->name))
+			break;
+	ensure(i != peer->channels_c, warnx(
+		"channel_exit(): channel had user, but user hadn't channel"
+	), 1)
+	peer->channels[i] = peer->channels[--peer->channels_c];
+
+	if (!channel->users_c)
+	{
+		for (i = 0; i < channels_c; ++i)
+			if (!strcmp(channels[i].name, channel->name))
+				break;
+		ensure(i != channels_c, warnx(
+			"channel_exit(): couldn't find empty channel"
+		), 1);
+		channels[i] = channels[--channels_c];
+	}
+
+	return 0;
+}
diff --git a/handle.c b/handle.c
index 79a3b9a..61b34f7 100644
--- a/handle.c
+++ b/handle.c
@@ -20,18 +20,46 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <err.h>
 
-#define ensure(cond, iffalse, doexit) if (!(cond)) { iffalse; if (doexit >= 0) return doexit; }
+
+static int
+join(struct Message *msg, struct Peer *peer)
+{
+	size_t i, j;
+
+	/* TODO: support comma separated list of channels, keys */
+	ensure(peer->type, reply(peer, 451), 0);
+	ensure(msg->params[0] && *msg->params[0], reply(peer, 461), 0);
+	ensure(peer->channels_c != lengthof(peer->channels), reply(peer, 405, msg->params[0]), 0)
+	ensure(*msg->params[0] == '#', reply(peer, 403, msg->params[0]), 0);
+
+	for (i = 0; i < channels_c; ++i)
+	{
+		if (strcmp(channels[i].name, msg->params[0]))
+			continue;
+
+		for (j = 0; j < channels[i].users_c; ++j)
+			ensure(
+				strcmp(channels[i].users[j]->nick, peer->nick),
+				(void)0,
+				0
+			);
+
+		ensure(!channel_join(&channels[i], peer), reply(peer, 471), 0);
+		return 0;
+	}
+
+	channels[channels_c] = (struct Channel){0};
+	strlcpy(channels[channels_c].name, msg->params[0], sizeof(channels->name));
+	channel_join(&channels[channels_c++], peer);
+	return 0;
+}
 
 static int
 nick(struct Message *msg, struct Peer *peer)
 {
-	ensure(msg->params[0] && msg->params[0], writef(
-		peer->fd,
-		":%s 431 %s :No nickname given",
-		hostname,
-		getnick(peer)
-	), 0);
+	ensure(msg->params[0] && msg->params[0], reply(peer, 431), 0);
 
 	user_reg(peer, msg->params[0], NULL, NULL);
 
@@ -39,31 +67,43 @@ nick(struct Message *msg, struct Peer *peer)
 }
 
 static int
+part(struct Message *msg, struct Peer *peer)
+{
+	size_t i;
+
+	ensure(peer->type, reply(peer, 451), 0);
+	ensure(msg->params[0] && *msg->params[0], reply(peer, 461), 0);
+
+	for (i = 0; i < peer->channels_c; ++i)
+		if (!strcmp(peer->channels[i]->name, msg->params[0]))
+			break;
+	ensure(i != peer->channels_c, reply(peer, 442, msg->params[0]), 0)
+
+	writechanf(
+		0,
+		peer->channels[i],
+		":%s!%s@%s PART %s :%s",
+		getnick(peer),
+		peer->user,
+		peer->host,
+		msg->params[0],
+		msg->params[1] ? msg->params[1] : getnick(peer)
+	);
+	channel_exit(peer->channels[i], peer);
+	return 0;
+}
+
+static int
 privmsg(struct Message *msg, struct Peer *peer)
 {
 	size_t i;
 
-	ensure(peer->type, writef(
-		peer->fd,
-		":%s 451 %s :You have not registered",
-		hostname,
-		getnick(peer)
-	), 0);
-	ensure(msg->params[0] && *msg->params[0], writef(
-		peer->fd,
-		":%s 411 %s :No recipient given (PRIVMSG)",
-		hostname,
-		getnick(peer)
-	), 0);
-	ensure(msg->params[1] && *msg->params[1], writef(
-		peer->fd,
-		":%s 412 %s :No text to send",
-		hostname,
-		getnick(peer)
-	), 0);
+	ensure(peer->type, reply(peer, 451), 0);
+	ensure(msg->params[0] && *msg->params[0], reply(peer, 411), 0);
+	ensure(msg->params[1] && *msg->params[1], reply(peer, 412), 0);
 
 	/* TODO: implement channels */
-	ensure(!strchr("!+#&", *msg->params[0]), writef(
+	ensure(!strchr("!+&", *msg->params[0]), writef(
 		peer->fd,
 		":%s 421 %s :Channels are not supported yet",
 		hostname,
@@ -71,17 +111,48 @@ privmsg(struct Message *msg, struct Peer *peer)
 	), 0);
 
 	/* TODO: implement server-to-server communication */
-	for (i = 0; i < PEERS_MAX; ++i)
-		if (peers[i].type == CLIENT && !strcmp(peers[i].nick, msg->params[0]))
+	switch (*msg->params[0])
+	{
+	case '#':
+	{
+		for (i = 0; i < channels_c; ++i)
+			if (!strcmp(channels[i].name, msg->params[0]))
+			{
+				writechanf(
+					peer,
+					&channels[i],
+					":%s!%s@%s PRIVMSG %s :%s",
+					peer->nick,
+					peer->user,
+					peer->host,
+					msg->params[0],
+					msg->params[1]
+				);
+				break;
+			}
+		ensure(i != channels_c, reply(peer, 401, msg->params[0]), 0);
+	} break;
+	default:
+	{
+		for (i = 0; i < peers_c; ++i)
+		{
+			if (peers[i].type != CLIENT || strcmp(peers[i].nick, msg->params[0]))
+				continue;
+
 			writef(
 				peers[i].fd,
 				":%s!%s@%s PRIVMSG %s :%s",
 				peer->nick,
 				peer->user,
 				peer->host,
-				peer->nick,
+				msg->params[0],
 				msg->params[1]
 			);
+			break;
+		}
+		ensure(i != peers_c, reply(peer, 401, msg->params[0]), 0);
+	} break;
+	}
 	return 0;
 }
 
@@ -96,20 +167,9 @@ user(struct Message *msg, struct Peer *peer)
 {
 	size_t i;
 
-	ensure(!peer->type, writef(
-		peer->fd,
-		":%s 462 %s :Unauthorized command (already registered)",
-		hostname,
-		getnick(peer)
-	), 0);
+	ensure(!peer->type, reply(peer, 462), 0);
 	for (i = 0; i < 4; ++i)
-		ensure(msg->params[i] && *msg->params[i], writef(
-			peer->fd,
-			":%s 461 %s %s :Not enough parameters",
-			hostname,
-			getnick(peer),
-			msg->command
-		), 0);
+		ensure(msg->params[i] && *msg->params[i], reply(peer, 461), 0);
 
 	user_reg(peer, NULL, msg->params[0], msg->params[3]);
 
@@ -119,9 +179,9 @@ user(struct Message *msg, struct Peer *peer)
 static int
 default_handler(struct Message *msg, struct Peer *peer)
 {
-	ensure(peer->type, 0, 0);
+	ensure(peer->type, (void)0, 0);
 
-	writef(peer->fd, ":%s 421 %s %s :Unknown command", hostname, getnick(peer), msg->command);
+	reply(peer, 421, msg->command);
 	return 0;
 }
 
@@ -130,7 +190,9 @@ static struct Handler {
 	Handler *handler;
 } handlers[] =
 {
+	{ "join", join },
 	{ "nick", nick },
+	{ "part", part },
 	{ "privmsg", privmsg },
 	{ "quit", quit },
 	{ "user", user },
diff --git a/ircd.h b/ircd.h
index 5e787da..8097c26 100644
--- a/ircd.h
+++ b/ircd.h
@@ -21,11 +21,16 @@
 
 
 #define PARAM_MAX 15
-#define PEERS_MAX 512
+
 #define MESSAGE_MAX 512
+#define PEERS_MAX 512
+#define CHANNELS_MAX 512
+#define USERS_PER_CHANNEL 512
+#define MODES_MAX 512
 
 #define BIT(x) x##_BIT, x = 1 << x##_BIT, x##_BIT_ = x##_BIT
 #define lengthof(X) (sizeof(X) / sizeof(*(X)))
+#define ensure(cond, iffalse, doexit) if (!(cond)) { iffalse; if (doexit >= 0) return doexit; }
 
 struct Message
 {
@@ -35,13 +40,16 @@ struct Message
 struct Peer
 {
 	int fd;
-	enum ClientType { UNREGED, CLIENT, SERVER, SERVICE } type;
+	enum ClientType { UNREGD, CLIENT, SERVER, SERVICE } type;
 	char nick[16], user[16], real[32], host[64];
-	char **channels;
-	size_t channels_count;
-	char modes[52];
+	struct Channel *channels[32];
+	enum PeerStatus {
+		BIT(DELETE),
+		BIT(ANNOUNCE),
+	} flags;
 	char buf[MESSAGE_MAX];
-	size_t recvd;
+	size_t recvd, channels_c;
+	char modes[52];
 };
 
 struct Channel
@@ -51,13 +59,16 @@ struct Channel
 	struct {
 		char mode;
 		char param[MESSAGE_MAX - 1];
-	} *modes;
-	size_t modes_count;
+	} modes[MODES_MAX];
+	struct Peer *users[USERS_PER_CHANNEL];
+	size_t modes_c, users_c;
 };
 
 typedef int Handler(struct Message *msg, struct Peer *peer);
 
+extern struct Channel channels[CHANNELS_MAX];
 extern struct Peer peers[PEERS_MAX];
+extern size_t channels_c, peers_c;
 extern const char *hostname;
 extern const char *host;
 extern int port;
@@ -65,10 +76,15 @@ extern int port;
 const char *getnick(const struct Peer *peer);
 void user_reg(struct Peer *peer, const char *nick, const char *user, const char *real);
 
+int channel_join(struct Channel *channel, struct Peer *peer);
+int channel_exit(struct Channel *channel, struct Peer *peer);
+
 int parse_message(char *buf, struct Message *msg);
 int handle(struct Peer *peer);
+int reply(const struct Peer *peer, int number, ...);
 Handler *find(const char *command);
 int writef(int fd, const char *fmt, ...);
+int writechanf(const struct Peer *except, const struct Channel *channel, const char *fmt, ...);
 void ircd(void);
 
 #endif /* __IRCD_H__ */
diff --git a/loop.c b/loop.c
index ba14a4d..b2430b8 100644
--- a/loop.c
+++ b/loop.c
@@ -29,9 +29,9 @@
 #include <sys/types.h>
 
 
-struct Peer peers[PEERS_MAX];
 static struct pollfd pfd[PEERS_MAX + 1];
-static size_t remove_schedule[PEERS_MAX];
+struct Peer peers[PEERS_MAX];
+size_t peers_c;
 
 const char *host;
 int port;
@@ -39,7 +39,7 @@ int port;
 void
 ircd(void)
 {
-	size_t i, schedulei, connected, pid;
+	size_t i, j, k;
 	struct sockaddr_in addr, clientaddr;
 	int res, sfd, client;
 	nfds_t passed;
@@ -51,9 +51,7 @@ ircd(void)
 	res = inet_pton(AF_INET, host, &addr.sin_addr);
 	if (res == -1)
 		err(1, "inet_pton()");
-
-	sfd = socket(AF_INET, SOCK_STREAM, 0);
-	if (sfd == -1)
+	sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1)
 		err(1, "socket()");
 
 	res = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
@@ -69,27 +67,27 @@ ircd(void)
 		err(1, "listen()");
 
 	signal(SIGPIPE, SIG_IGN);
-	for (connected = 0;;schedulei = 0)
+	for (peers_c = channels_c = 0;;)
 	{
-		pfd[connected] = (struct pollfd){ .fd = sfd, .events = POLLIN };
-		for (i = 0; i < connected; ++i)
+		pfd[peers_c] = (struct pollfd){ .fd = sfd, .events = POLLIN };
+		for (i = 0; i < peers_c; ++i)
 			pfd[i] = (struct pollfd)
 			{
 				.fd = peers[i].fd,
 				.events = POLLIN,
 			};
-		res = poll(pfd, (passed = connected) + 1, -1);
+		res = poll(pfd, (passed = peers_c) + 1, -1);
 		if (res == -1)
 			err(1, "poll()");
 
-		if (pfd[connected].revents & POLLIN)
+		if (pfd[peers_c].revents & POLLIN)
 		{
 			client = accept(sfd, (void *)&clientaddr, &(int){sizeof(clientaddr)});
 			if (client == -1)
 			{
-				warn("accept(sfd)");
+				warn("accept(%d)", sfd);
 			}
-			else if (connected == PEERS_MAX)
+			else if (peers_c == PEERS_MAX)
 			{
 				/*
 				 * TODO: maybe send 005 or smth?
@@ -98,9 +96,9 @@ ircd(void)
 			}
 			else
 			{
-				peers[connected++] = (struct Peer){ .fd = client };
+				peers[peers_c++] = (struct Peer){ .fd = client };
 				strlcpy(
-					peers[connected - 1].host,
+					peers[peers_c - 1].host,
 					inet_ntoa(clientaddr.sin_addr),
 					sizeof(peers->host)
 				);
@@ -119,16 +117,46 @@ ircd(void)
 			);
 			peers[i].recvd += recvd;
 			if (recvd == -1 || !recvd || handle(&peers[i]))
-				remove_schedule[schedulei++] = i;
+				peers[i].flags |= DELETE;
 		}
-		for (i = 0; i < schedulei; ++i)
+		for (i = 0; i < peers_c; ++i)
 		{
-			pid = remove_schedule[i];
-			/* TODO: announce_quit(&peers[pid]); */
-			writef(peers[pid].fd, "ERROR :Closing Link: %s", peers[pid].host);
-			close(peers[pid].fd);
-			peers[pid] = peers[--connected];
+			if (!(peers[i].flags & DELETE))
+				continue;
+
+			if (!peers[i].type)
+				goto skip;
+			writef(
+				peers[i].fd,
+				":%s!%s@%s QUIT",
+				getnick(&peers[i]),
+				peers[i].user,
+				peers[i].host
+			);
+			for (j = 0; j < peers[i].channels_c; ++j)
+			{
+				channel_exit(peers[i].channels[j], &peers[i]);
+				for (k = 0; k < peers[i].channels[j]->users_c; ++k)
+					peers[i].channels[j]->users[k]->
+						flags |= ANNOUNCE;
+			}
+			for (j = 0; j < peers_c; ++j)
+				if (peers[j].flags & ANNOUNCE)
+				{
+					writef(
+						peers[j].fd,
+						"%s!%s@%s QUIT",
+						getnick(&peers[i]),
+						peers[i].user,
+						peers[i].host
+					);
+					peers[j].flags &= ~ANNOUNCE;
+				}
+skip:
+			writef(peers[i].fd, "ERROR :Closing Link: %s", peers[i].host);
+			close(peers[i].fd);
+			peers[i] = peers[--peers_c];
+			i = 0;
 		}
-		schedulei = 0;
 	}
 }
diff --git a/reply.c b/reply.c
new file mode 100644
index 0000000..b73fbea
--- /dev/null
+++ b/reply.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org>
+ *
+ * Permission to use, copy, modify, and 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 "ircd.h"
+
+#include <stdarg.h>
+
+#include <err.h>
+
+
+typedef int Reply(const struct Peer *peer, va_list ap);
+
+#define REPLY(n) static int r##n (const struct Peer *peer, va_list ap)
+#define ARG(n) const char *n = va_arg(ap, const char *)
+
+REPLY(1)
+{
+	return writef(
+		peer->fd,
+		":%s 001 %s Welcome to the Internet Relay Network %s!%s@%s",
+		hostname,
+		getnick(peer),
+		getnick(peer),
+		peer->user,
+		peer->host
+	);
+}
+
+REPLY(401)
+{
+	ARG(name);
+	return writef(
+		peer->fd,
+		":%s 401 %s %s :No such nick/channel",
+		hostname,
+		getnick(peer),
+		name
+	);
+}
+
+REPLY(403)
+{
+	ARG(channel);
+	return writef(
+		peer->fd,
+		":%s 403 %s %s :No such channel",
+		hostname,
+		getnick(peer),
+		channel
+	);
+}
+
+REPLY(405)
+{
+	ARG(channel);
+	return writef(
+		peer->fd,
+		":%s 405 %s %s :You have joined too many channels",
+		hostname,
+		getnick(peer),
+		channel
+	);
+}
+
+REPLY(411)
+{
+	return writef(
+		peer->fd,
+		":%s 411 %s :No recipient given (PRIVMSG)",
+		hostname,
+		getnick(peer)
+	);
+}
+
+REPLY(412)
+{
+	return writef(
+		peer->fd,
+		":%s 412 %s :No text to send",
+		hostname,
+		getnick(peer)
+	);
+}
+
+REPLY(421)
+{
+	ARG(command);
+	return writef(
+		peer->fd,
+		":%s 421 %s %s :Unknown command",
+		hostname,
+		getnick(peer),
+		command
+	);
+}
+
+REPLY(431)
+{
+	return writef(
+		peer->fd,
+		":%s 431 %s :No nickname given",
+		hostname,
+		getnick(peer)
+	);
+}
+
+REPLY(442)
+{
+	ARG(channel);
+	return writef(
+		peer->fd,
+		":%s 442 %s %s :You're not on that channel",
+		hostname,
+		getnick(peer),
+		channel
+	);
+}
+
+REPLY(451)
+{
+	return writef(
+		peer->fd,
+		":%s 451 %s :You have not registered",
+		hostname,
+		getnick(peer)
+	);
+}
+
+REPLY(461)
+{
+	ARG(command);
+	return writef(
+		peer->fd,
+		":%s 461 %s %s :Not enough parameters",
+		hostname,
+		getnick(peer),
+		command
+	);
+}
+
+REPLY(462)
+{
+	return writef(
+		peer->fd,
+		":%s 462 %s :Unauthorized command (already registered)",
+		hostname,
+		getnick(peer)
+	);
+}
+
+REPLY(471)
+{
+	ARG(channel);
+	return writef(
+		peer->fd,
+		":%s 471 %s %s :Cannot join channel (+l)",
+		hostname,
+		getnick(peer),
+		channel
+	);
+}
+
+#undef REPLY
+#define REPLY(n) [n] = r##n
+static Reply *replies[] = {
+	REPLY(  1),
+	REPLY(401),
+	REPLY(403),
+	REPLY(405),
+	REPLY(411),
+	REPLY(412),
+	REPLY(421),
+	REPLY(431),
+	REPLY(442),
+	REPLY(451),
+	REPLY(461),
+	REPLY(462),
+	REPLY(471),
+};
+
+int
+reply(const struct Peer *peer, int number, ...)
+{
+	Reply *choice;
+	va_list args;
+	int ret;
+
+	choice = replies[number];
+	if (!choice)
+		warnx("YOU FUCKING STUPID SHIT IMPLEMENT REPLY #%d NOW!!!", number);
+	va_start(args, number);
+	ret = choice(peer, args);
+	va_end(args);
+
+	return ret;
+}
diff --git a/user.c b/user.c
index 4411144..e37452b 100644
--- a/user.c
+++ b/user.c
@@ -37,14 +37,6 @@ user_reg(struct Peer *peer, const char *nick, const char *user, const char *real
 	if (*peer->nick && *peer->user && *peer->real)
 	{
 		peer->type = CLIENT;
-		writef(
-			peer->fd,
-			":%s 001 %s :Welcome to the Internet Relay Network %s!%s@%s",
-			hostname,
-			getnick(peer),
-			getnick(peer),
-			peer->user,
-			peer->host
-		);
+		reply(peer, 1);
 	}
 }
diff --git a/writef.c b/writef.c
index 7d57e3b..e010ca4 100644
--- a/writef.c
+++ b/writef.c
@@ -23,18 +23,45 @@
 #include <unistd.h>
 
 
+static char buf[MESSAGE_MAX + 1];
+static int written;
+
+static void
+vstoref(const char *fmt, va_list ap)
+{
+	written = vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
+	written = written < sizeof(buf) - 2 ? written : sizeof(buf) - 2;
+	memcpy(buf + written, "\r\n", 2);
+	written += 2;
+}
+
 int
 writef(int fd, const char *fmt, ...)
 {
-	static char buf[MESSAGE_MAX + 1];
 	va_list args;
-	int written;
 
 	va_start(args, fmt);
-	written = vsnprintf(buf, sizeof(buf) - 2, fmt, args);
+	vstoref(fmt, args);
 	va_end(args);
-	written = written < sizeof(buf) - 2 ? written : sizeof(buf) - 2;
-	memcpy(buf + written, "\r\n", 3);
 
-	return write(fd, buf, written + 3);
+	return write(fd, buf, written);
+}
+
+int
+writechanf(const struct Peer *except, const struct Channel *channel, const char *fmt, ...)
+{
+	va_list args;
+	int count;
+	size_t i;
+
+	va_start(args, fmt);
+	vstoref(fmt, args);
+	va_end(args);
+
+	count = 0;
+	for (i = 0; i < channel->users_c; ++i)
+		if (!except || channel->users[i]->fd != except->fd)
+			count += write(channel->users[i]->fd, buf, written) == written;
+
+	return count;
 }