aboutsummaryrefslogtreecommitdiffstats
path: root/c/irc.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-17 23:03:43 +0200
committeruser <user@clank>2026-07-17 23:03:43 +0200
commit3b856b3d009b99d01cb84727b803735937a44d16 (patch)
tree30c12ac18a260dea12f27688013cbd46870248e0 /c/irc.c
parentgbhub: control socket + gbtype to inject shell input without the OSK (diff)
downloadgbos-3b856b3d009b99d01cb84727b803735937a44d16.tar.gz
gbos-3b856b3d009b99d01cb84727b803735937a44d16.tar.xz
gbos-3b856b3d009b99d01cb84727b803735937a44d16.zip
irc: strip mIRC formatting, drop 004/005 noise, fix a RAM-bank-swap landmine
Against the real irc.sl0p.foo (via a TLS-stripping socat proxy) the client's screen filled with garbage after connect. Two causes: 1. Color/format codes. The MOTD is a mIRC-color ASCII-art logo. We dropped the \x03 control byte (term ignores <32) but left its numeric *arguments*, so the logo rendered as digit soup ('09> stream the work', '030903 030903 ...'). strip_fmt() now removes \x03 color (+its fg[,bg] digits), \x04 hex color, and the \x02/\x0f/\x11/\x16/\x1d/ \x1e/\x1f toggles, applied to the trailing text of every line (leaving \x01 for CTCP). Standalone digits like '3 apples' are untouched. 2. The real bug: handle() defaulted pfx/txt to a read-only "" literal, and both bang(pfx) and strip_fmt(txt) write a NUL terminator into it. That literal lives in ROM (000-FFF); on MBC5 a write there is a RAM-bank-select, silently swapping the cart-RAM bank - where every static lives - out from under the program. One prefix-less or empty-trailing server line (real ircds send them: NOTICE AUTH, ERROR, registration PING) and all state turns to garbage. Fixed with a writable 1-byte 'empty' default (zeroed in main; gbos doesn't clear BSS). Also suppress 004 (MYINFO) and 005 (ISUPPORT) - pure noise on 40 cols, and InspIRCd splits ISUPPORT across several lines. Verified end to end against the live server: full InspIRCd MOTD (logo, LUSERS, links) renders clean and stable; host unit tests cover color/ bold/CTCP-action stripping and prefix-less NOTICE/ERROR/PING.
Diffstat (limited to 'c/irc.c')
-rw-r--r--c/irc.c43
1 files changed, 42 insertions, 1 deletions
diff --git a/c/irc.c b/c/irc.c
index 817471e..551dca1 100644
--- a/c/irc.c
+++ b/c/irc.c
@@ -30,6 +30,12 @@ static unsigned char namebuf[64];
static unsigned char dst[4];
static unsigned char sock;
static unsigned char quitting;
+/* A WRITABLE empty string for parse defaults. handle() writes terminators
+ into pfx (bang) and txt (strip_fmt); a read-only "" literal lives in ROM
+ ($4000-$5FFF), where a write is an MBC5 *RAM-bank-select* - it would swap
+ the whole $A000 data bank out from under every static (instant garbage).
+ A prefix-less or empty-trailing server line is enough to trigger it. */
+static char empty[1];
/* ---- tiny string helpers (SDCC libc is us) ------------------------------- */
static unsigned char sceq(const char *a, const char *b) { /* case-insens. */
@@ -107,9 +113,41 @@ static void bang(char *p) {
static unsigned char isnum(const char *c) {
return c[0] >= '0' && c[0] <= '9';
}
+static unsigned char ishex(char c) {
+ return (c>='0'&&c<='9')||(c>='a'&&c<='f')||(c>='A'&&c<='F');
+}
+
+/* Strip mIRC/IRC formatting in place - crucially their *arguments*, or a
+ color logo (a real MOTD!) leaves its \x03 color numbers on screen as digit
+ soup. \x03 = color (up to 2 digits[,up to 2 digits]); \x04 = hex color
+ (up to 6 hex[,6 hex]); \x02 bold \x0f reset \x11 mono \x16 reverse
+ \x1d italic \x1e strike \x1f underline are lone toggles. Leaves \x01
+ (CTCP) intact for the PRIVMSG path. */
+static void strip_fmt(char *s) {
+ char *w = s, *r = s;
+ unsigned char c, d;
+ while (*r) {
+ c = (unsigned char)*r;
+ if (c == 3) {
+ r++; d = 0;
+ while (*r>='0'&&*r<='9'&&d<2) { r++; d++; }
+ if (*r==','&&r[1]>='0'&&r[1]<='9') { r++; d=0; while(*r>='0'&&*r<='9'&&d<2){r++;d++;} }
+ continue;
+ }
+ if (c == 4) {
+ r++; d = 0;
+ while (ishex(*r)&&d<6) { r++; d++; }
+ if (*r==','&&ishex(r[1])) { r++; d=0; while(ishex(*r)&&d<6){r++;d++;} }
+ continue;
+ }
+ if (c==2||c==15||c==17||c==22||c==29||c==30||c==31) { r++; continue; }
+ *w++ = *r++;
+ }
+ *w = 0;
+}
static void handle(char *l) {
- char *pfx = (char *)"", *cmd, *arg, *txt = (char *)"";
+ char *pfx = empty, *cmd, *arg, *txt = empty;
char *p = l;
if (*p == ':') { /* :prefix */
@@ -127,6 +165,7 @@ static void handle(char *l) {
p++;
}
/* arg = first param (NUL-joined list), txt = trailing */
+ strip_fmt(txt); /* kill color/format codes + args */
if (sceq(cmd, "PING")) {
slen = 0; sc("PONG :"); sc(txt[0] ? txt : arg); sfin();
@@ -210,6 +249,7 @@ static void handle(char *l) {
return;
}
if (cmd[1]=='3'&&cmd[0]=='3') return; /* 333 topic-by: skip */
+ if (cmd[0]=='0'&&cmd[1]=='0'&&(cmd[2]=='4'||cmd[2]=='5')) return; /* MYINFO/ISUPPORT noise */
if (txt[0]) say2("-!- ", txt); /* motd & friends */
return;
}
@@ -315,6 +355,7 @@ void main(void) {
parent's RAM bank) - initialize every piece of state explicitly */
llen = inlen = drawn = slen = quitting = 0;
target[0] = 0;
+ empty[0] = 0; /* gbos doesn't zero BSS */
argp = getargs(); /* copy args before seg is used */
for (i = 0; argp[i] && i < 63; i++) namebuf[i] = (unsigned char)argp[i];