From 27ca8c0758aac8df7d8541451429acb7d9e56d73 Mon Sep 17 00:00:00 2001 From: MrLenin <909621+MrLenin@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:43:59 -0400 Subject: [PATCH] shun: defer add-time ident match when client's USER hasn't landed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add-time apply loop (do_shun) was bypassing the ident half of a user@host shun for any client whose cli_user->username was still empty — pre-USER clients in mid-registration where cli_user is allocated but the USER command hasn't been parsed yet. Guard at lines 221-223 (introduced 2019 in f3179734) short-circuited the whole ident check on empty username, so a shun like `bob@*` matched every mid-registration client, and the server emitted a spurious `SNO_GLINE` "Shun active for " oper notice attributing the shun to a user whose ident doesn't (yet) match. Fix: always defer pre-USER, except when sh_user is the literal single bare `*` — the only shape we can prove matches any ident. The explicit `sh_user[1] == '\0'` check is needed so non-bare patterns that lead with `*` (e.g. `*foo` = "ends with foo") don't sneak through the shortcut. This preserves the original "no identd running, mask must be `*@host`" use case: a `*@1.2.3.4` shun still applies pre-USER. All other shapes (`bob@*`, `*foo@*`, `?bob@*`, `?*@*`, etc.) defer — and shun_lookup invoked from m_privmsg/etc. re-evaluates them once USER lands, so deferral has no enforcement consequence. The only artefact this suppresses is the spurious oper notice for the wrong user. The matching else-branch already handled the empty-username case correctly (it only ran match() when username was non-empty), so the fix is localised to the early-defer guard. --- ircd/shun.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ircd/shun.c b/ircd/shun.c index 48a47ce5..130e462b 100644 --- a/ircd/shun.c +++ b/ircd/shun.c @@ -218,6 +218,34 @@ do_shun(struct Client *cptr, struct Client *sptr, struct Shun *shun) continue; Debug((DEBUG_DEBUG,"Matched!")); } else { /* Host/IP shun */ + /* Defer when the client's username isn't known yet (pre-USER + * mid-registration — cli_user is allocated by make_user on + * first input, but cli_user->username[] isn't populated until + * the USER command is parsed in register_user). We can't + * evaluate the ident half of a user@host mask without it; any + * guard on the *shape* of sh_user (`starts with *', `looks + * specific', etc.) leaves edge cases broken (`?*', `?bob', + * `*foo' which is "ends with foo" not "matches anything", + * etc.) — so the rule is just "defer unless we know the mask + * matches any ident." + * + * The only safe known-match-any shortcut is the literal + * single bare `*' — explicitly check sh_user[0]=='*' && + * sh_user[1]=='\0' so patterns that start with star but have + * trailing chars (e.g. `*foo') don't short-circuit. This + * preserves Jobe's original intent (a `*@host' shun applies + * pre-USER, useful when no identd is running) while closing + * the false-positive class where a `bob@*' shun previously + * fired for any mid-registration client. + * + * Post-registration shun_lookup invoked from m_privmsg/etc. + * re-evaluates the shun once the client's USER lands, so the + * deferral has no enforcement consequence — the only + * artefact it suppresses is the spurious SNO_GLINE "Shun + * active for ..." oper notice for the wrong user. */ + if (!*(cli_user(acptr)->username) + && !(shun->sh_user[0] == '*' && shun->sh_user[1] == '\0')) + continue; if (*(cli_user(acptr)->username) && match(shun->sh_user, (cli_user(acptr))->username) != 0) continue;