Commit graph

318 commits

Author SHA1 Message Date
5dcd216eeb
api: ordered Rank enum (founder>admin>op>halfop>voice) replaces the coarse u8 access rank, so PEACE distinguishes every tier
All checks were successful
CI / check (push) Successful in 3m59s
2026-07-17 17:33:53 +00:00
f2ba876ce4
botserv: bots join +ao (protected admin + op), keeping op alongside protect
All checks were successful
CI / check (push) Successful in 3m53s
2026-07-17 17:11:25 +00:00
73467b91ca
botserv: bots join channels as protected admins (+a) so ordinary ops can't kick or deop them
All checks were successful
CI / check (push) Successful in 3m50s
2026-07-17 16:52:00 +00:00
e2330c56ff
chanserv: SOP protect (+ao) and founder owner (+qo) with standard ~ & @ prefix tiers
All checks were successful
CI / check (push) Successful in 4m12s
2026-07-17 16:10:36 +00:00
b5aef650f2
chanserv: all op-and-above tiers grant +o for a single @ prefix; tier distinction stays in the access list
All checks were successful
CI / check (push) Successful in 4m12s
2026-07-17 15:58:11 +00:00
2f6fd41465
chanserv: SOP protect tier over AOP (+ao), founder owner (+qo); resolve op access by capability not mode string
All checks were successful
CI / check (push) Successful in 3m59s
2026-07-17 15:43:16 +00:00
f5c976eb56
api/gameserv: type email ExpiryTarget and chess Ending as enums (drop stringly returns)
All checks were successful
CI / check (push) Successful in 3m53s
2026-07-17 14:15:39 +00:00
7a5f1502a4
infoserv: type the news kind as an enum at the store boundary
All checks were successful
CI / check (push) Successful in 3m53s
2026-07-17 13:53:53 +00:00
ef34c04c8d
operserv: type the X-line kind as an enum at the command/store boundary, wire token in storage 2026-07-17 13:47:10 +00:00
2c8ba4460d
forbid: type the FORBID kind as an enum at the store boundary, string only in storage
All checks were successful
CI / check (push) Successful in 3m56s
2026-07-17 13:26:16 +00:00
1fb3615b7e
auth: warn on unknown [[oper]] privilege names instead of silently dropping them
All checks were successful
CI / check (push) Successful in 3m53s
2026-07-17 12:45:41 +00:00
e3b83f4a92
operserv: REHASH announces the reload in the log channel via DebugServ
All checks were successful
CI / check (push) Successful in 4m1s
2026-07-17 11:02:29 +00:00
64b675e61f
operserv: REHASH reloads config live without a restart
All checks were successful
CI / check (push) Successful in 3m54s
2026-07-17 10:46:12 +00:00
e112b8737c
auth: deferred login is authoritative in the account map, not just ircd metadata
All checks were successful
CI / check (push) Successful in 3m46s
2026-07-17 03:30:47 +00:00
75c6537532
standard replies: emit IRCv3 FAIL/WARN/NOTE for service errors, config-gated
All checks were successful
CI / check (push) Successful in 3m42s
2026-07-17 02:53:48 +00:00
fcca7ac12b
chanserv: channel seen checks channel membership, not global presence
All checks were successful
CI / check (push) Successful in 3m45s
2026-07-17 01:54:55 +00:00
66eb644a07
chanserv: channel-scoped seen with last message, replied in-channel
All checks were successful
CI / check (push) Successful in 3m47s
2026-07-17 01:41:47 +00:00
f2774f13fe
services: all pseudo-clients join a configurable channel (default #services)
All checks were successful
CI / check (push) Successful in 3m39s
2026-07-17 01:23:18 +00:00
657154e268
services: assigned bot fronts channel modes, not chanserv
All checks were successful
CI / check (push) Successful in 3m41s
2026-07-17 01:17:29 +00:00
96654b5e5a
groupserv: gate channel access on the c flag, document !group
All checks were successful
CI / check (push) Successful in 3m43s
2026-07-17 01:00:27 +00:00
b87a0c56a2
services: label as network service in whois
All checks were successful
CI / check (push) Successful in 3m44s
2026-07-17 00:30:51 +00:00
eaf3111917 whois: answer IDLE for pseudo-clients 2026-07-16 23:22:35 +00:00
a60a6df19d debugserv: sourced auth feed, drop icons 2026-07-16 23:08:30 +00:00
9bdda721f3 keycard: redeem website login keycards over SASL 2026-07-16 22:27:59 +00:00
38d12cb0bb debugserv: live services activity feed 2026-07-16 22:27:35 +00:00
6c403c01b0 services: configurable pseudo-client host 2026-07-16 19:15:48 +00:00
2f9790feac
auth: offload IDENTIFY + SASL PLAIN verify off the engine lock
All checks were successful
CI / check (push) Successful in 3m47s
Both ran scram verify_plain (~1s PBKDF2 at 1.2M iters) inline under the engine
lock, freezing the whole daemon per login. Add NetAction::DeferAuthenticate +
AuthThen continuation + ctx.defer_authenticate, mirroring DeferPassword: the
module/SASL path fetches the verifier cheaply and defers; the link layer runs
verify_plain on spawn_blocking, then Engine::complete_authenticate finishes the
login (IDENTIFY reuses the same ctx helpers, so its login/AJOIN/vhost/memo
side-effects are unchanged). Tests resolve the defer inline (cfg(test)). The
gRPC web-login path was already fixed; no login path stalls services now.
(GHOST/DROP/CERT/GROUP still verify inline but are rare account ops, not logins.)
2026-07-16 19:04:18 +00:00
63fc2fb2c0
grpc: run authenticate's PBKDF2 verify off the engine lock
All checks were successful
CI / check (push) Successful in 3m54s
The Authenticate RPC held the engine Mutex across scram verify_plain, which at
production iteration counts (1.2M) is ~0.8s of PBKDF2 on the async reactor —
freezing the whole daemon (IRC link, gossip, every RPC) for the duration and
serializing all logins. Fetch the verifier under the lock (cheap), then run
verify_plain on spawn_blocking, lock released. 6 concurrent logins: 823ms (was
~4460ms serialized). set_password/register already offload this way; drop the
now-unused authority_authenticate.
2026-07-16 18:40:30 +00:00
2e92f75a90
chanserv: PEACE never blocks acting on yourself (self-deop)
All checks were successful
CI / check (push) Successful in 3m58s
!deop with no target deops the caller, but peace_blocks compared the target's
access rank to the caller's — equal for self — so PEACE refused it with 'you
can't act against someone with equal or higher access.' Exempt the self case
(target uid == caller uid) in peace_blocks: acting on yourself isn't acting
against anyone, so you can always drop your own status. Covers DEOP/DEVOICE and
the kick/ban paths too.
2026-07-16 17:59:00 +00:00
1f6a11dba2
statserv: persist per-channel activity across restart too
All checks were successful
CI / check (push) Successful in 3m49s
The earlier StatsSet fix persisted only the shared namespaced counters. The
per-channel BOTSTATS view (lines seen + top talkers, from /statserv #chan) lived
on the LiveChannel struct, which is rebuilt from each burst — so it reset to zero
on restart. Move it to a name-keyed Network.chan_activity map (independent of live
membership, seedable before the burst) and carry it in the StatsSet snapshot
(new serde-default `channels` field) alongside the counters. Reworded the
notice from 'seen this session' now that it persists.
2026-07-16 17:50:11 +00:00
8a9b6a37ca
botserv: op assigned bots on join; statserv: persist counters across restart
Some checks failed
CI / check (push) Failing after 3m48s
Problem 1: an assigned bot joined via IJOIN with no status mode, so it had no
+o. Join it with the 4-param IJOIN form (ts 1, mode o) so it's opped, matching
Anope's default bot modes (+o), and exempt bots from SECUREOPS/RESTRICTED so the
op is never stripped.

Problem 2: the shared stat counters lived only in memory, so a restart wiped
them. Snapshot them to the log (StatsSet event, folded into NetData + compaction)
periodically and on shutdown, and reseed the live registry from the log at
startup. Live gauges (online counts) stay re-derived, not stored.
2026-07-16 17:32:20 +00:00
34bc7d69f0
grpc: provision sets the verifier on an already-existing account
All checks were successful
CI / check (push) Successful in 3m50s
An Anope import creates accounts (channel ownership, vhosts, certs) but can't
carry the one-way password hash, so they exist without a verifier. provision
then refused them as AlreadyExists, so the website's backfill could never give
migrated members a credential — every one was locked out of password login.
Make provision update the verifier in place on an existing account (logged as
AccountPasswordSet), and treat an empty scram512 as absent consistently in
apply(). Keyed by name, so imported channel/vhost/ban data stays attached.
2026-07-16 16:53:06 +00:00
9d94dd5eb0
chanserv: support param mode locks (+f flood, +j joinflood)
Some checks failed
CI / check (push) Has been cancelled
Mode locks were char-only, so Anope FLOOD/JOINFLOOD locks were dropped on
migration. Carry a value per locked param-mode through the event log, MLOCK
parsing/display, drift enforcement, compaction, and the Anope importer, so a
migrated network keeps its flood protection verbatim.
2026-07-16 15:23:09 +00:00
062eae703e
add Anope->Echo migration importer (echo import)
All checks were successful
CI / check (push) Successful in 3m41s
New 'echo import <anope.json> <out.log> [node]' subcommand builds a fresh event
log from an Anope db_json: accounts (identity only — Anope's one-way password
hashes can't become SCRAM verifiers, so migrated users auth by certfp/external
authority), grouped nicks, cert fingerprints, vhosts, channels (+desc/topic/
assigned bot), access mapped to op/voice by each channel's AUTOOP/AUTOVOICE
thresholds, param-less mode locks, BotServ bots (service pseudo-clients skipped),
and memos. Read-only on the source; self-verifies by replaying the produced log.
Verified against the live backup (11 accounts, 10 channels, 10 bots) + a unit
test over a synthetic fixture. The prerequisite for a safe cutover.
2026-07-16 13:05:45 +00:00
b23bf5d8a9
test: property-test gossip convergence between two nodes
All checks were successful
CI / check (push) Successful in 3m45s
A fresh peer that backfills a node's log via the version vector must reach the
same global state (accounts, akills, forbids, groups); channels/jupes are Local
and correctly don't replicate. Guards the core federation guarantee — a
distinct invariant from same-node replay (exercises missing_for + version_vector
+ cross-node ingest).
2026-07-16 11:17:43 +00:00
56cac370eb
test: cover removal/mutation events in the parity property tests
Some checks failed
CI / check (push) Has been cancelled
Extends build_broad_state with vhost/suspend/akill/forbid/news/cert/access/
group-member removals and a memo read — the asymmetric removal apply arms are
exactly where the drop-purge bug hid. Both the fold-parity and compaction
round-trip tests pass over the full ~55-operation sequence, so live==replay
==compacted holds across adds, mutations and removals.
2026-07-16 11:15:57 +00:00
289470ca0c
db: compaction was silently dropping channel and jupe state
All checks were successful
CI / check (push) Successful in 3m52s
compact() rebuilds channels field-by-field, and it omitted several fields, so
automatic log compaction (should_compact -> compact) silently deleted them:
channel url, email, successor, noexpire, the restricted/noautoop/bot_greet/
nobot settings, and every jupe. Emits them all now (settings gated on !=
default via a new PartialEq). Adds a compaction round-trip property test
(compact()+reopen == before) alongside the fold-parity one; both share the
broad build/snapshot helpers.
2026-07-16 11:08:55 +00:00
8c55347d80
test: broaden the fold-parity property test to the full event surface
All checks were successful
CI / check (push) Successful in 3m49s
Extends the round-trip live-vs-replay test to ~45 event types (bots, news,
reports, help tickets, oper grants, session exceptions, vhost forbid/template,
entrymsg, noexpire, oper notes, group founder, default bot, account email).
All pass — jupes was the only divergence — so this now guards essentially
every event's live mutation against its apply() arm.
2026-07-16 11:02:07 +00:00
90167dd46f
db: persist jupes across restart (event-source them)
All checks were successful
CI / check (push) Successful in 3m50s
A fold-parity property test caught it: jupes were plain in-memory Db fields
that logged no event, so after any services restart every juped server was
silently un-juped and could relink. Moved jupes into NetData (like akills/
forbids/groups) and event-sourced them with JupeAdded/JupeRemoved (Local
scope), so a jupe now replays from the log. Adds the round-trip property test
(live state == replayed state) as a permanent guard against this bug class.
2026-07-16 10:56:22 +00:00
61005f52f5
services: throttle emailed codes to stop RESEND/RESETPASS email-bombing
All checks were successful
CI / check (push) Successful in 3m46s
REGISTER was rate-limited but RESEND and RESETPASS weren't — anyone could
repeatedly request confirmation/reset codes for any account with an address
on file, flooding the victim's inbox and burning the service's sender
reputation. A per-account 60s cooldown (code_issue_wait, mirroring the vhost
request throttle) now gates both paths; guessing was already infeasible
(2^40 code + 5-try limit), so this closes the abuse, not an auth hole.
2026-07-16 10:41:52 +00:00
320a591ae3
engine: track the server tree so a hub SQUIT cascades
All checks were successful
CI / check (push) Successful in 3m50s
SQUIT arrives once for a splitting server (by SID), but a hub takes its
whole subtree with it — users behind downstream servers were left as stale
state. The parser now distinguishes a sourced downstream SERVER (tracked
with its parent) from the unsourced uplink handshake, the engine keeps the
SID->parent tree, and a SQUIT forgets every user across the departed
server's subtree. Tree is rebuilt each link. Wire formats verified against
the InspIRCd 4 m_spanningtree source.
2026-07-16 10:31:29 +00:00
e5e04b4130
engine: reintroduce a killed service agent
All checks were successful
CI / check (push) Successful in 3m46s
A KILL of a services pseudo-client only reintroduced BotServ bots; a killed
core agent (NickServ, ChanServ, …) fell through to the forget-user path and
stayed off the network until a services restart. UserKilled now recognises an
agent by its fixed uid and re-emits its introduction, so an oper can't KILL
services down.
2026-07-16 10:16:48 +00:00
4aff734340
engine: forget a split server's users on SQUIT
All checks were successful
CI / check (push) Successful in 3m47s
A netsplit is signalled once via SQUIT, not as a QUIT per user, so every
user behind the departed server lingered in services' state — stale
sessions, session-limit slots, and channel memberships. The parser now
surfaces ServerSplit and the engine forgets every uid carrying that
server's SID prefix, leaving other servers' users untouched.
2026-07-16 10:10:20 +00:00
63ecd79b12
engine: handle inbound KILL — forget users, reintroduce bots
KILL was never parsed, so a killed user lingered in services' state (stale
session, session-limit slot, channel membership) and a killed services bot
vanished for good. The parser now surfaces UserKilled; a killed real user is
forgotten like a QUIT (shared forget_user helper), while a killed bot is
dropped and reconciled so it is reintroduced and rejoins its channels.
2026-07-16 10:06:36 +00:00
a9a3408de0
engine: a kicked services bot rejoins its assigned channel
A PART/KICK of a bot uid updated channel membership but not bot_channels,
so reconcile still believed the bot was present and never rejoined it — an
op could evict a services bot for good. The Part handler now drops the stale
membership and reconciles, so a bot assigned to the channel comes right back.
2026-07-16 10:03:00 +00:00
7e868babc3
inspircd: restore logins from replayed accountname metadata
All checks were successful
CI / check (push) Successful in 3m45s
The inbound parser ignored METADATA entirely, so on a services netburst
(after a restart/relink) the ircd's replayed 'accountname' metadata was
dropped — every logged-in user looked logged out to services until they
re-identified, losing ChanServ access. Parse METADATA <uid> accountname
into a new AccountLogin event that restores account_of (empty = logout);
our own echoes and other keys are filtered.
2026-07-16 09:54:35 +00:00
99df7baf10
engine: apply gossiped network bans to the ircd immediately
All checks were successful
CI / check (push) Successful in 3m47s
A local AKILL/SQLINE/etc. pushes an ADDLINE to the ircd at once, but a ban
arriving by gossip was only held for the next netburst — so a user banned on
one network could keep hopping to peers until their next relink. ingest now
signals BanAdded/BanLifted (all xline kinds), and gossip_ingest pushes the
ADDLINE/DELLINE. Renamed AccountChange to IngestEffect to match its broader
role.
2026-07-16 09:28:56 +00:00
886fe65e4d
engine: log out local sessions on a gossiped account suspension
All checks were successful
CI / check (push) Successful in 3m43s
A local SUSPEND logs out the account's active sessions, but a suspension
arriving by gossip only set the flag — a session already identified on a
remote node stayed logged in despite the network-wide suspension. ingest
now signals AccountChange::Suspended, and gossip_ingest ends those sessions
(the account and its channels are kept). Factors the per-session logout into
a shared logout_uid helper.
2026-07-16 09:13:53 +00:00
58af1187fc
engine: part orphaned-channel bots on a gossiped account removal
All checks were successful
CI / check (push) Successful in 3m46s
When a peer's gossiped entry drops or takes over an account, the local node
releases the channels that account founded locally (Local-scope channels can
be founded by a Global, gossiped account). That cleanup runs outside the
dispatch path, so a bot assigned to a released channel lingered until the
next netburst. gossip_ingest now reconciles bots after an account is removed,
matching the expiry sweep.
2026-07-16 08:57:38 +00:00
bbea17e848
engine: part a bot when its channel expires
All checks were successful
CI / check (push) Successful in 3m47s
Command-driven channel drops reconcile bots via the dispatch path, but the
inactivity-expiry sweep runs outside it — so a bot assigned to an expired
(or expired-founder-orphaned) channel lingered there until the next
netburst. The sweep now reconciles at the end, parting any stranded bot.
2026-07-16 08:27:59 +00:00