Skip to content

Channel rename support: RN handler, AC R rename arbitration - #57

Draft
MrLenin wants to merge 8 commits into
evilnet:masterfrom
MrLenin:feature/channel-rename
Draft

Channel rename support: RN handler, AC R rename arbitration#57
MrLenin wants to merge 8 commits into
evilnet:masterfrom
MrLenin:feature/channel-rename

Conversation

@MrLenin

@MrLenin MrLenin commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Adds services-side support for IRCv3 draft/channel-rename against nefarious2's services-arbitration machinery. Draft: functionally blocked on the upstream nefarious2 patch that emits the rename query and RN token — the X3 side is complete and E2E-verified against the fork carrying that machinery.

Split: the registered-mode +R remap that used to ride in this PR is now its own PR (#60) — merge that first, since the ircd only arbitrates renames for channels it sees as registered. The BX-P bouncer commits this branch previously sat on top of are #56 and have been dropped from here.

What this does

  • RenameChannel() primitive + rename hook array (hash.c/h): re-keys a live channel under a new name — new tail-allocated node, members/banlists moved with backpointers re-pointed, channels dict re-keyed (the key is an interior pointer into the node), locks inherited, per-module hooks (reg_channel_rename_func) fired with both nodes alive. Hooks added for chanserv, opserv, spamserv, helpserv, snoop, track, blacklist covering every persistent chanNode* holder (audited exhaustively). Hook registered regardless of bot presence.
  • AC R disambiguation (proto-p10.c): the rename permission query AC <unum> R <cookie> <#chan> RENAME <new> is now distinguished from the legacy account stamp (argc>=7 + RENAME keyword). This also fixes a real bug: the stamp path previously consumed the query's cookie as an account stamp, silently discarding the requesting user's legitimate stamp for the session. The reply carries an explicit RENAME discriminator (AC <cookie> A RENAME / AC <cookie> D RENAME :<reason>) so the ircd can route it by cookie without a FindNServer() guess.
  • Owner-only authorization (chanserv_rename_allowed): denies below UL_OWNER, protected/suspended channels, bad/DNR'd/already-registered/overlong target names (CHANNELLEN cap — IsChannelName has no length ceiling).
  • RN handler: applies the network rename to X3's state (authorize-at-query, apply-at-RN); places a timed DNR on the old name (rename_dnr_duration, default 1d) so it can't be re-registered out from under the rename.
  • Advertises the r server flag (rename-capable) in the SERVER line so an upstream-style ircd delivers RN to us.
  • NULL-bot guards; RenameChannel result checked in cmd_rename.

Verification

Live E2E on the Afternet testnet against the fork: owner rename approved and applied with X3's registration following (INFO/ACCESS/MYACCESS track the new name), op-level user denied with the owner-only reason surfaced to the client, account stamp intact after a denied query, old name DNR-refused post-rename. Vitest suite (6 cases) in the testnet repo (channel-rename-services.test.ts).

🤖 Generated with Claude Code

MrLenin and others added 8 commits August 30, 2026 02:04
Implement Task 1 of the channel-rename foundation: RenameChannel() primitive
and rename hook array in X3's hash layer.

The implementation follows the existing del-channel hook pattern (dcf_list):
- reg_channel_rename_func() registers rename handlers with optional context
- Hook array (crf_list) and extra-data list (crf_list_extra) with dynamic
  growth on first use (8-entry initial, doubling on capacity exhaustion)
- RenameChannel(old_node, new_name) creates the new node with all state
  copied, updates dict keys, then fires hooks with BOTH nodes alive

Key design constraint: old node's dict key is an interior pointer to name[];
removal must happen before free(). Hooks receive both nodes because
pointer-compare holders need old node address while name-keyed dicts need
old->name intact for any final cleanup lookups.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 61f8e11)
Task 2 of the channel-rename plan: each module that keeps a persistent
struct chanNode * now registers a channel_rename_func_t hook (Task 1,
83af825) next to its existing reg_del_channel_func/reg_new_channel_func
call, so a RenameChannel() call re-points every holder instead of
leaving it dangling on the freed old node.

- chanserv.c: new_chan->channel_info->channel back-pointer (the
  memcpy in RenameChannel moves channel_info onto new_chan but leaves
  chanData->channel targeting old_chan); walks adduser_pendings;
  swaps matches in chanserv_conf.support_channels.
- opserv.c: compare-swap debug_channel/alert_channel/staff_auth_channel;
  walks opserv_user_alerts re-pointing each alert's
  discrim->channels[0..channel_count); removes the pending
  opserv_part_channel purge-lock timer for old_chan without re-adding
  (mirrors opserv_channel_delete, but ignores func too since the node
  is about to be freed); recomputes bad_channel on the new node.
- spamserv.c: follows spamserv_cs_move_merge's existing chanInfo
  re-point + registered_channels_dict re-key pattern; additionally
  walks every connected_users_dict entry's spam/flood/joinflood node
  chains re-pointing ->channel.
- mod-helpserv.c: walks helpserv_bots_dict re-pointing hs->helpchan
  and each hs->page_targets[PGSRC_COUNT]; re-keys the single
  helpserv_bots_bychan_dict entry for the renamed channel (key is the
  interior helpchan->name pointer).
- mod-snoop.c, mod-track.c, mod-blacklist.c: single compare-swap of
  each module's channel config slot.

mod-blacklist.c has a pre-existing compile break unrelated to this
change (reg_new_user_func/reg_exit_func called with too few args,
predating this commit) that already excludes it from the configured
--enable-modules set; the new hook and its registration call are
correctly typed and were verified with a standalone compile of just
those lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b62a9f6)
The legacy P10 "AC <target> R <account>" stamp and the new ircd rename
permission query "AC <unum> R <cookie> <#chan> RENAME <new>" both use
subcommand R, and both send the SAME low argc/argv[3] positioning that
made them collide: naively treating every R as an account stamp would
poison the account cache with a cookie or a channel name instead of a
handle. Disambiguate on shape (argc >= 7 && argv[5] == "RENAME") before
falling through to the unchanged legacy call_account_func() path.

The rename branch replies with the cookie as parv[1] ("AC <cookie> A"
or "AC <cookie> D :<reason>"), NOT the LOC reply shape ("AC <servnum>
A <cookie>") — ircd's m_account.c keys pending renames on parv[1]
failing a server-numeric lookup, so the cookie must lead.

Authorization policy (chanserv_rename_allowed, chanserv.c/.h): owner
(UL_OWNER, access 500) only, mirroring cmd_move's DNR-against-the-new-
name gate, minus the IsHelping/"force" bypass — staff override here
comes only from _GetChannelUser()'s override=1 synthetic access entry.
Also denies on unauthenticated requester, protected/suspended source
channel, a blocked or already-registered new name, and unregistered
target channels are allowed through untouched (nothing to protect).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a21f605)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit eb236a0)
…name

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 867c97f)
…annelName)

IsChannelName() has no length ceiling, allowing overlong channel names to
enter fixed-size sprintf buffers in hash.c and chanserv.c. Added length
validation in RenameChannel() and chanserv_rename_allowed() to reject names
exceeding CHANNELLEN (200 bytes).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit cad2a2d)
reg_channel_rename_func(chanserv_channel_rename, NULL) was registered
inside init_chanserv()'s if(nick) block, but saxdb_register("ChanServ",
...) is unconditional. With the bot nick disabled (the "." convention),
registered channels still load from the DB via chanserv_saxdb_read, so
channel_info exists and is live -- but with the rename hook never
registered, an RN arriving over the wire runs RenameChannel() with no
callback to repoint channel_info->channel at the new node. It's left
pointing at the chanNode RenameChannel() just freed: a use-after-free at
the next saxdb write (or any other access through channel_info->channel).

chanserv_channel_rename() only touches channel_info, adduser_pendings,
and chanserv_conf.support_channels -- none of which depend on the bot
nick being enabled -- so it's safe to register unconditionally. Moved it
out of if(nick), alongside the other already-unconditional hook
registrations (reg_handle_rename_func, reg_unreg_func).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2b0e89c)
Change 1: irc_server() now emits +s6or (was +s6o) on both the initial
J10 self-burst line and the relay P10/J10 line, advertising the ircu
upstream-standard r (rename-capable) SERVER flag so an r-aware ircd
delivers RN tokens to this link. No-op on the fork, which routes
channel-rename traffic via IsService rather than the r flag.

Change 2 (F2): the AC rename-permission reply (cmd_account, the R
subtype) now carries an explicit RENAME discriminator token right
after the A/D type: "AC <unum> A RENAME" / "AC <unum> D RENAME
:<reason>". Previously the reply shape was indistinguishable on the
wire from an AC LOC reply once the receiving ircd fell through its
FindNServer() check — a decimal rename cookie could alias a live
server numeric and get misrouted. The discriminator lets ms_account()
on both ircd trees route by cookie unconditionally, without ever
calling FindNServer() on the rename path. Companion ircd-side changes
land in the nefarious and nefarious-upstream repos (m_account.c),
same commit message, same branch strategy (feature/backport
channel-rename); all three must ship together since X3 now always
emits the RENAME token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d213568)
@MrLenin
MrLenin force-pushed the feature/channel-rename branch from 1d80d7d to 1415576 Compare August 30, 2026 06:07
@MrLenin MrLenin changed the title Channel rename support: RN handler, AC R rename arbitration, registered channels marked +R Channel rename support: RN handler, AC R rename arbitration Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant