summary refs log tree commit diff
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2026-02-10 03:38:15 +0300
committerNakidai <nakidai@disroot.org>2026-02-10 03:38:15 +0300
commitdb996c22cd51cd46dd9afee1085a8af04cc7b53b (patch)
tree3fdc59fc9f7d42a1e8f96d9ca615e2d8b19a60da
parent15b0b805fe2e739c9d89b54e1bfb26beb79c3112 (diff)
downloadlibreircd-db996c22cd51cd46dd9afee1085a8af04cc7b53b.tar.gz
libreircd-db996c22cd51cd46dd9afee1085a8af04cc7b53b.zip
Add channel mode editing support
Well, an oper should have some way to manage their channel :)
-rw-r--r--channel.c54
-rw-r--r--handle.c52
-rw-r--r--ircd.h14
-rw-r--r--reply.c16
4 files changed, 134 insertions, 2 deletions
diff --git a/channel.c b/channel.c
index 21a777a..d04f3cc 100644
--- a/channel.c
+++ b/channel.c
@@ -79,6 +79,60 @@ channel_exit(struct Channel *channel, struct Peer *peer)
 	return 0;
 }
 
+const struct ChannelModeParam *
+channel_modes_parse(const struct Message *msg)
+{
+	static struct ChannelModeParam modes[MESSAGE_MAX];
+	size_t i, modes_c, queue_l, queue_r;
+	char *m, queue[PARAM_MAX];
+	int set = 1;
+
+	memset(modes, 0, sizeof(modes));
+	for (modes_c = queue_l = queue_r = 0, i = 1; msg->params[i]; ++i, set = 1) switch (*msg->params[i])
+	{
+	case '-':
+		set = 0;
+	case '+':
+	{
+		for (m = msg->params[i]+1; *m; ++m) switch (*m)
+		{
+		break; case 'o':
+			/* since all flags are ASCII (< 128) bit 7
+			 * can be and is abused for the set flag */
+			if (queue_r == lengthof(queue))
+			{
+				modes[modes_c].mode = *m;
+				return modes;
+			}
+			queue[queue_r++] = (set << 7) | *m;
+		break; case 's':
+			modes[modes_c].mode = *m;
+			modes[modes_c++].set = set;
+		break; default:
+			modes[modes_c].mode = *m;
+			modes[modes_c++].set = set;
+			return modes;
+		}
+	} break;
+	default:
+	{
+		modes[modes_c].param = msg->params[i];
+
+		if (queue_l == queue_r)
+		{
+			return modes;
+		}
+		else
+		{
+			modes[modes_c].set = !!(queue[queue_l] & (1 << 7));
+			modes[modes_c++].mode = queue[queue_l++] & ~(1 << 7);
+		}
+	} break;
+	}
+
+	return modes;
+}
+
 void
 channel_remove(size_t cid)
 {
diff --git a/handle.c b/handle.c
index ecbde08..d11b39a 100644
--- a/handle.c
+++ b/handle.c
@@ -76,8 +76,10 @@ join(struct Message *msg, struct Peer *peer)
 static int
 mode_channel(struct Message *msg, struct Peer *peer)
 {
+	const struct ChannelModeParam *parsed;
 	enum ChannelPeerMode modes;
 	struct Channel *channel;
+	char modebuf[2];
 	size_t i;
 
 	for (i = 0; i < channels_c; ++i)
@@ -96,6 +98,56 @@ mode_channel(struct Message *msg, struct Peer *peer)
 
 	ensure(modes & CHANNEL_OPER || peer->modes & OPER, reply(peer, 482, channel->name), 0);
 
+	parsed = channel_modes_parse(msg);
+	for (; parsed->param || parsed->mode; ++parsed) switch (parsed->mode)
+	{
+	break; case 'o':
+		ensure(parsed->param, reply(peer, 461, msg->command), 0);
+
+		for (i = 0; i < channel->peers_c; ++i)
+			if (!strcmp(channel->peers[i].p->nick, parsed->param))
+				break;
+		ensure(i != channel->peers_c, reply(peer, 441, parsed->param, channel->name), 0);
+
+		writechanf(
+			0,
+			channel,
+			"%s!%s@%s MODE %s %co %s",
+			getnick(peer),
+			peer->user,
+			peer->host,
+			channel->name,
+			parsed->set ? '+' : '-',
+			parsed->param
+		);
+
+		if (parsed->set)
+			channel->peers[i].modes |= CHANNEL_OPER;
+		else
+			channel->peers[i].modes &= ~CHANNEL_OPER;
+	break; case 's':
+		/* TODO: implement +s. For now it's just a some mode with no param */
+		writechanf(
+			0,
+			channel,
+			"%s!%s@%s MODE %s %cs",
+			getnick(peer),
+			peer->user,
+			peer->host,
+			channel->name,
+			parsed->set ? '+' : '-'
+		);
+		if (parsed->set)
+			channel->modes |= CHANNEL_SECRET;
+		else
+			channel->modes &= ~CHANNEL_SECRET;
+	break; default:
+		modebuf[0] = parsed->mode;
+		modebuf[1] = 0;
+		reply(peer, 472, modebuf, channel->name);
+		return 0;
+	}
+
 	return 0;
 }
 
diff --git a/ircd.h b/ircd.h
index 29457d8..931948b 100644
--- a/ircd.h
+++ b/ircd.h
@@ -61,6 +61,13 @@ struct Peer
 	time_t last, ping;
 };
 
+struct ChannelModeParam
+{
+	char *param;
+	int set;
+	char mode;
+};
+
 struct Channel
 {
 	enum { GLOBAL, LOCAL, MODELESS, SAFE } type;
@@ -72,7 +79,11 @@ struct Channel
 			BIT(CHANNEL_VOICE),
 		} modes;
 	} peers[CHANNEL_PEERS_MAX];
-	size_t modes_c, peers_c;
+	size_t peers_c;
+	enum ChannelMode
+	{
+		BIT(CHANNEL_SECRET),
+	} modes;
 };
 
 struct Oper
@@ -101,6 +112,7 @@ void user_remove(size_t pid);
 
 int channel_join(struct Channel *channel, struct Peer *peer);
 int channel_exit(struct Channel *channel, struct Peer *peer);
+const struct ChannelModeParam *channel_modes_parse(const struct Message *msg);
 void channel_remove(size_t cid);
 
 int parse_message(char *buf, struct Message *msg);
diff --git a/reply.c b/reply.c
index adc2557..de1d5c7 100644
--- a/reply.c
+++ b/reply.c
@@ -87,7 +87,7 @@ vreply(const struct Peer *peer, int number, va_list ap)
 		creation
 	), _);
 	REPLY(4, WRITE(
-		":%s 004 %s :%s libreircd-" IRCD_VERSION " aiwroOs :",
+		":%s 004 %s :%s libreircd-" IRCD_VERSION " aiwroOs os",
 		hostname,
 		getnick(peer),
 		hostname
@@ -220,6 +220,13 @@ vreply(const struct Peer *peer, int number, va_list ap)
 		getnick(peer),
 		nick
 	), nick, _);
+	REPLY(441, WRITE(
+		":%s 441 %s %s %s :They aren't on that channel",
+		hostname,
+		getnick(peer),
+		nick,
+		channel
+	), nick, channel, _);
 	REPLY(442, WRITE(
 		":%s 442 %s %s :You're not on that channel",
 		hostname,
@@ -253,6 +260,13 @@ vreply(const struct Peer *peer, int number, va_list ap)
 		getnick(peer),
 		channel
 	), channel, _);
+	REPLY(472, WRITE(
+		":%s 472 %s %s :is unknown mode char to me for %s",
+		hostname,
+		getnick(peer),
+		mode,
+		channel
+	), mode, channel, _);
 	REPLY(481, WRITE(
 		":%s 481 %s :Permission Denied- %s",
 		hostname,