diff options
| author | user <user@clank> | 2026-09-24 13:09:34 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 13:09:34 +0200 |
| commit | a73911fcbc5d7d6910d1edcdca38230a519361ca (patch) | |
| tree | 9e7a3a2774153e50ce8aea3ab3a7d93b1b91cd72 | |
| parent | usr: ls -l, df, mv, tail, tee, sleep, help, poweroff (diff) | |
| download | gbos-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.c | 52 |
1 files changed, 42 insertions, 10 deletions
@@ -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; } |
