aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-24 13:09:34 +0200
committeruser <user@clank>2026-09-24 13:09:34 +0200
commita73911fcbc5d7d6910d1edcdca38230a519361ca (patch)
tree9e7a3a2774153e50ce8aea3ab3a7d93b1b91cd72
parentusr: ls -l, df, mv, tail, tee, sleep, help, poweroff (diff)
downloadgbos-a73911fcbc5d7d6910d1edcdca38230a519361ca.tar.gz
gbos-a73911fcbc5d7d6910d1edcdca38230a519361ca.tar.xz
gbos-a73911fcbc5d7d6910d1edcdca38230a519361ca.zip
ircd: enforce unique nicknames (433), announce renames, fold case
NICK overwrote the nick with whatever was asked for, so two clients could both be "alice": /NAMES listed her twice and a private message only ever reached whichever slot came first in the table. - A nick already held by another client is refused with 433 ERR_NICKNAMEINUSE and the client keeps the one it had. Unregistered clients simply stay unregistered until they pick a free one, which is what every client already expects. - A NICK from a registered client is a rename: it is announced to the client and to its channel, so nobody's user list goes stale. - IRC names fold case, so "Alice" is "alice" - for the uniqueness check, for PRIVMSG by nick, and for channels ("#GB" is "#gb"). One irceq() replaces the exact-match compare that was used for all three. gbos's own irc(1) already retries 433 by appending '_', so two Game Boys with the default nick now sort themselves out: verified with a GB running ircd, a laptop client holding "sl0pboy", and a second GB arriving as "sl0pboy_" - both in #gbos, messages crossing both ways.
-rw-r--r--usr/ircd.c52
1 files changed, 42 insertions, 10 deletions
diff --git a/usr/ircd.c b/usr/ircd.c
index b960dd6..eccaf82 100644
--- a/usr/ircd.c
+++ b/usr/ircd.c
@@ -41,13 +41,6 @@ static unsigned char mlen;
static char line[INBUF]; /* the line being dispatched */
/* ---- tiny string helpers -------------------------------------------------- */
-static unsigned char seq(const char *a, const char *b) {
- while (*a && *a == *b) {
- a++;
- b++;
- }
- return (unsigned char)(*a == *b);
-}
static unsigned char up(char c) { return (unsigned char)((c >= 'a' && c <= 'z') ? c - 32 : c); }
/* case-insensitive compare of a command word */
static unsigned char cmdis(const char *w, const char *name) {
@@ -58,6 +51,16 @@ static unsigned char cmdis(const char *w, const char *name) {
}
return (unsigned char)(*w == 0);
}
+/* IRC names - nicknames AND channels - are case-insensitive, so "Alice" and
+ "alice" are one person, "#GB" and "#gb" one channel. (RFC 1459 also folds
+ []\\~ onto {}|^; plain ASCII is close enough for a Game Boy.) */
+static unsigned char irceq(const char *a, const char *b) {
+ while (*a && up(*a) == up(*b)) {
+ a++;
+ b++;
+ }
+ return (unsigned char)(*a == 0 && *b == 0);
+}
static void scpy(char *d, const char *s, unsigned char max) {
unsigned char i = 0;
while (s[i] && i < max - 1) {
@@ -100,7 +103,15 @@ static void bcast(const char *chan, unsigned char except) {
unsigned char i;
if (!chan[0]) return;
for (i = 0; i < MAXCL; i++)
- if (cused[i] && creg[i] && i != except && seq(cchan[i], chan)) msend(i);
+ if (cused[i] && creg[i] && i != except && irceq(cchan[i], chan)) msend(i);
+}
+
+/* is `n` already in use by some OTHER client? */
+static unsigned char nick_taken(const char *n, unsigned char except) {
+ unsigned char i;
+ for (i = 0; i < MAXCL; i++)
+ if (cused[i] && i != except && cnick[i][0] && irceq(cnick[i], n)) return 1;
+ return 0;
}
static void logline(const char *a, const char *b) {
@@ -163,7 +174,7 @@ static void names(unsigned char ci) {
m(cchan[ci]);
m(" :");
for (i = 0; i < MAXCL; i++)
- if (cused[i] && creg[i] && seq(cchan[i], cchan[ci])) {
+ if (cused[i] && creg[i] && irceq(cchan[i], cchan[ci])) {
m(cnick[i]);
m(" ");
}
@@ -204,6 +215,27 @@ static void dispatch(unsigned char ci) {
if (cmdis(cmd, "NICK")) {
if (!a1[0]) return;
+ if (nick_taken(a1, ci)) { /* 433: keep the nick they have */
+ mnumeric(ci, "433");
+ m(a1);
+ m(" :Nickname is already in use");
+ mend();
+ msend(ci);
+ return;
+ }
+ if (irceq(cnick[ci], a1)) { /* same nick, only case: just take it */
+ scpy(cnick[ci], a1, NICKSZ);
+ return;
+ }
+ if (creg[ci]) { /* a rename: everyone must be told */
+ mprefix(ci);
+ m("NICK :");
+ m(a1);
+ mend();
+ msend(ci);
+ bcast(cchan[ci], ci);
+ logline(cnick[ci], " renamed");
+ }
scpy(cnick[ci], a1, NICKSZ);
if (cgotu[ci] && !creg[ci]) welcome(ci);
return;
@@ -279,7 +311,7 @@ static void dispatch(unsigned char ci) {
nl();
} else {
for (i = 0; i < MAXCL; i++) /* to one nick */
- if (cused[i] && creg[i] && seq(cnick[i], a1)) msend(i);
+ if (cused[i] && creg[i] && irceq(cnick[i], a1)) msend(i);
}
return;
}