about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2026-01-10 16:32:54 +0300
committerNakidai <nakidai@disroot.org>2026-01-10 16:32:54 +0300
commit76e2c978829bc6ff7bfe6c49bfa3739e19370990 (patch)
tree07851d4ee5ca107ce4ec18889b20c006f56f53d3
parent346abf24c900a19a77a8ce66566a7ffd86a819bb (diff)
downloadlibreircd-76e2c978829bc6ff7bfe6c49bfa3739e19370990.tar.gz
libreircd-76e2c978829bc6ff7bfe6c49bfa3739e19370990.zip
Add basic user mode handling
So, now there're all modes listed in RFC 2812. The only what they
do now, though, is a +r restricting user from changing their nick
-rw-r--r--handle.c46
-rw-r--r--ircd.h15
-rw-r--r--loop.c2
-rw-r--r--main.c3
-rw-r--r--reply.c32
-rw-r--r--user.c36
6 files changed, 123 insertions, 11 deletions
diff --git a/handle.c b/handle.c
index 87dfa77..9d705e0 100644
--- a/handle.c
+++ b/handle.c
@@ -57,11 +57,52 @@ join(struct Message *msg, struct Peer *peer)
 }
 
 static int
+mode(struct Message *msg, struct Peer *peer)
+{
+	int set = 1, m;
+
+	ensure(peer->type, reply(peer, 451), 0);
+	ensure(msg->params[0] && msg->params[0], reply(peer, 431), 0);
+	ensure(msg->params[1] && msg->params[1], reply(peer, 221), 0);
+
+	switch (*msg->params[1])
+	{
+	case '-':
+		set = !set;
+	case '+':
+		++msg->params[1];
+	default:
+	{
+		for (; *msg->params[1]; ++msg->params[1])
+		{
+			m = user_mode(*msg->params[1]);
+			if (!m)
+			{
+				reply(peer, 501, msg->params[1]);
+				continue;
+			}
+
+			if (m & AWAY)
+				continue;
+
+			if (set && !(m & (OPER | LOCALOPER)))
+				peer->modes |= m;
+			else if (!(m & RESTRICTED))
+				peer->modes &= ~m;
+		}
+	}
+	}
+
+	return 0;
+}
+
+static int
 nick(struct Message *msg, struct Peer *peer)
 {
 	ensure(msg->params[0] && msg->params[0], reply(peer, 431), 0);
+	ensure(!(peer->modes & RESTRICTED), reply(peer, 484), 0);
 
-	user_reg(peer, msg->params[0], NULL, NULL);
+	user_reg(peer, msg->params[0], NULL, NULL, NULL);
 
 	return 0;
 }
@@ -205,7 +246,7 @@ user(struct Message *msg, struct Peer *peer)
 	for (i = 0; i < 4; ++i)
 		ensure(msg->params[i] && *msg->params[i], reply(peer, 461), 0);
 
-	user_reg(peer, NULL, msg->params[0], msg->params[3]);
+	user_reg(peer, NULL, msg->params[0], msg->params[3], msg->params[1]);
 
 	return 0;
 }
@@ -225,6 +266,7 @@ static struct Handler {
 } handlers[] =
 {
 	{ "join", join },
+	{ "mode", mode },
 	{ "nick", nick },
 	{ "part", part },
 	{ "privmsg", privmsg },
diff --git a/ircd.h b/ircd.h
index 6035b79..2c4b144 100644
--- a/ircd.h
+++ b/ircd.h
@@ -51,9 +51,17 @@ struct Peer
 		BIT(DELETE),
 		BIT(ANNOUNCE),
 	} flags;
+	enum PeerMode {
+		BIT(AWAY),
+		BIT(INVISIBLE),
+		BIT(WALLOPS),
+		BIT(RESTRICTED),
+		BIT(OPER),
+		BIT(LOCALOPER),
+		BIT(SNOTICE),
+	} modes;
 	char buf[MESSAGE_MAX];
 	size_t recvd, channels_c;
-	char modes[52];
 };
 
 struct Channel
@@ -75,10 +83,11 @@ extern struct Peer peers[PEERS_MAX];
 extern size_t channels_c, peers_c;
 extern const char *hostname;
 extern const char *host;
-extern int port;
+extern unsigned long port;
 
 const char *getnick(const struct Peer *peer);
-void user_reg(struct Peer *peer, const char *nick, const char *user, const char *real);
+enum PeerMode user_mode(char m);
+void user_reg(struct Peer *peer, const char *nick, const char *user, const char *real, const char *mode);
 void user_remove(size_t pid);
 
 int channel_join(struct Channel *channel, struct Peer *peer);
diff --git a/loop.c b/loop.c
index aa3f458..cfba4b6 100644
--- a/loop.c
+++ b/loop.c
@@ -34,7 +34,7 @@ struct Peer peers[PEERS_MAX];
 size_t peers_c;
 
 const char *host;
-int port;
+unsigned long port;
 
 void
 ircd(void)
diff --git a/main.c b/main.c
index c93a84d..26de6e6 100644
--- a/main.c
+++ b/main.c
@@ -16,6 +16,7 @@
 
 #include "ircd.h"
 
+#include <errno.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -38,7 +39,7 @@ main(int argc, char **argv)
 	hostname = argv[1];
 	host = argv[2];
 	port = strtoul(argv[3], &p, 10);
-	if (*p || !port || port > 65535)
+	if (errno || *p || !port || port > 65535)
 		errx(1, "invalid port");
 
 #ifdef __OpenBSD__
diff --git a/reply.c b/reply.c
index eaa56f4..2aa0e9b 100644
--- a/reply.c
+++ b/reply.c
@@ -29,8 +29,11 @@
 #define M5(x, ...) ARG(x); M4(__VA_ARGS__)
 #define M6(x, ...) ARG(x); M5(__VA_ARGS__)
 #define M7(x, ...) ARG(x); M6(__VA_ARGS__)
-#define GET_M(_0,_1,_2,_3,_4,_5,_6,_7,NAME,...) NAME
-#define ARGS(_0, ...) GET_M(_0,__VA_ARGS__,M7,M6,M5,M4,M3,M2,M1,M0,)(__VA_ARGS__,)
+#define M8(x, ...) ARG(x); M7(__VA_ARGS__)
+#define M10(x, ...) ARG(x); M9(__VA_ARGS__)
+#define M11(x, ...) ARG(x); M10(__VA_ARGS__)
+#define GET_M(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,NAME,...) NAME
+#define ARGS(_0, ...) GET_M(_0,__VA_ARGS__,M11,M10,M9,M8,M7,M6,M5,M4,M3,M2,M1,M0,)(__VA_ARGS__,)
 
 /*
  * REPLY macro allows to write less boilerplate for each IRC reply
@@ -71,6 +74,18 @@ vreply(const struct Peer *peer, int number, va_list ap)
 		peer->user,
 		peer->host
 	), _);
+	REPLY(221, WRITE(
+		":%s 221 %s +%s%s%s%s%s%s%s",
+		hostname,
+		getnick(peer),
+		peer->modes & AWAY ? "a" : "",
+		peer->modes & INVISIBLE ? "i" : "",
+		peer->modes & WALLOPS ? "w" : "",
+		peer->modes & RESTRICTED ? "r" : "",
+		peer->modes & OPER ? "o" : "",
+		peer->modes & LOCALOPER ? "O" : "",
+		peer->modes & SNOTICE ? "s" : ""
+	), _);
 	REPLY(401, WRITE(
 		":%s 401 %s %s :No such nick/channel",
 		hostname,
@@ -138,7 +153,18 @@ vreply(const struct Peer *peer, int number, va_list ap)
 		getnick(peer),
 		channel
 	), channel, _);
-	default: warn("unknown reply: %d", number); WRITE(
+	REPLY(484, WRITE(
+		":%s 484 %s :Your connection is restricted!",
+		hostname,
+		getnick(peer)
+	), _);
+	REPLY(501, WRITE(
+		":%s 501 %s :Unknown MODE flag (+%c)",
+		hostname,
+		getnick(peer),
+		*m
+	), m, _);
+	default: warn("unknown reply: %d", number); return WRITE(
 		":%s 421 %s err :Reply %d is not implemented yet",
 		hostname,
 		getnick(peer),
diff --git a/user.c b/user.c
index f856f6c..8131c84 100644
--- a/user.c
+++ b/user.c
@@ -16,7 +16,9 @@
 
 #include "ircd.h"
 
+#include <errno.h>
 #include <stddef.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <err.h>
@@ -28,15 +30,47 @@ getnick(const struct Peer *peer)
 	return *peer->nick ? peer->nick : "*";
 }
 
+enum PeerMode
+user_mode(char m)
+{
+	switch (m)
+	{
+	case 'a': return AWAY;
+	case 'i': return INVISIBLE;
+	case 'w': return WALLOPS;
+	case 'r': return RESTRICTED;
+	case 'o': return OPER;
+	case 'O': return LOCALOPER;
+	case 's': return SNOTICE;
+	default: return 0;
+	}
+}
+
 void
-user_reg(struct Peer *peer, const char *nick, const char *user, const char *real)
+user_reg(struct Peer *peer, const char *nick, const char *user, const char *real, const char *mode)
 {
+	unsigned long m;
+	const char *p;
+
 	if (nick)
 		strlcpy(peer->nick, nick, sizeof(peer->nick));
 	if (user)
 		strlcpy(peer->user, user, sizeof(peer->user));
 	if (real)
 		strlcpy(peer->real, real, sizeof(peer->real));
+	if (mode)
+	{
+		errno = 0;
+		m = strtoul(mode, &p, 10);
+		if (errno || *p)
+			goto skip;
+
+		if (m & 8)
+			peer->modes |= INVISIBLE;
+		if (m & 4)
+			peer->modes |= WALLOPS;
+	}
+skip:
 	if (*peer->nick && *peer->user && *peer->real)
 	{
 		peer->type = CLIENT;