Commit graph

39 commits

Author SHA1 Message Date
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
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
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
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
fa55bc3e3a
db: purge all references to a dropped account
All checks were successful
CI / check (push) Successful in 3m45s
Dropping (or expiring) an account left its channel access, channel
successorship, and group membership/foundership behind, keyed by name — so
re-registering the same nick silently inherited the old privileges. The
AccountDropped fold and the live drop_account path now share one helper
that erases every reference (founder channels are still handled by
handle_account_gone's successor transfer). Closes a privilege-inheritance
hole; especially relevant to inactivity expiry.
2026-07-16 03:51:01 +00:00
dc23a44f57
engine: apply auto-join, vhost and memo notice on SASL connect
All checks were successful
CI / check (push) Successful in 3m44s
A user authenticated via SASL during registration never runs the NickServ
IDENTIFY path, so they silently missed their auto-join channels, vhost, and
waiting-memo notice. The UserConnect handler now applies these login
side-effects when the arriving user is already authenticated, mirroring
identify.rs. Non-SASL connections are unaffected (no account set yet).
2026-07-16 03:35:32 +00:00
01ea0fa95e
ChanServ: add SET EMAIL channel contact address
Founders can attach a contact email to a channel, shown in INFO and
cleared when set empty. Mirrors SET URL exactly; Local-scope channel
metadata. Completes the channel DESC/URL/EMAIL trio.
2026-07-16 03:06:08 +00:00
7eb1adce5e
ChanServ: add SET URL channel homepage
Founders can attach a homepage URL to a channel; it shows in INFO and
clears when set empty, mirroring SET DESC. Local-scope (channel metadata),
not gossiped, consistent with the entrymsg/description events.
2026-07-16 02:58:44 +00:00
053f8cf832
NickServ: add SET HIDE STATUS for last-seen privacy
Lets an account keep its last-seen/online line in INFO visible only to
itself and to opers. Default stays public (Anope's default). Accepts
STATUS (with USERMASK as an alias) per Anope's SET HIDE <field> grammar.
2026-07-16 02:33:55 +00:00
39d7421c06
NickServ: show last-seen (and online status) in INFO
The account's last-active time was tracked but never surfaced. INFO now
reports it, reading 'now (online)' when the account has a live session.
Threads NetView into the INFO handler for the session lookup and carries
last_seen on AccountView.
2026-07-16 01:56:51 +00:00
2c4bc9079c
NickServ: add SET KILL to toggle nick protection
Nick protection was unconditional. SET KILL OFF lets an account opt out —
an unidentified user keeping one of its nicks is no longer prompted or
renamed to a guest. Stored inverted (no_protect) so protection stays the
default. Accepts Anope's ON/QUICK/IMMED/OFF; grace is a fixed interval
here, so the finer variants simply enable protection like ON.
2026-07-16 01:43:10 +00:00
87e21ff85f
NickServ: add SET AUTOOP per-account auto-op opt-out
A user with SET AUTOOP OFF is never auto-opped on join, even where they
hold channel access — they op themselves via ChanServ UP. Stored inverted
(no_autoop) so the default stays auto-op. Join gates on both the channel
setting and the account preference; either can opt out.
2026-07-16 01:37:31 +00:00
0027decdb7
OperServ: add SET READONLY lockdown
All checks were successful
CI / check (push) Successful in 3m31s
2026-07-16 01:05:48 +00:00
dd43b9a331
BotServ: add BOTLIST and AUTOASSIGN default bot
All checks were successful
CI / check (push) Successful in 3m33s
2026-07-16 00:57:06 +00:00
d819b2c75f
MemoServ: add RSEND read receipts
Some checks failed
CI / check (push) Has been cancelled
2026-07-16 00:43:01 +00:00
2850620be1
ChanServ: add SET AUTOOP
All checks were successful
CI / check (push) Successful in 3m35s
SET AUTOOP {ON|OFF} controls whether access members are auto-opped/voiced
on join (on by default). Stored inverted as a settings flag so the
default stays on, and gated in the join handler.
2026-07-16 00:36:19 +00:00
2c282d036f
MemoServ: add SET NOTIFY and SET LIMIT
All checks were successful
CI / check (push) Successful in 3m35s
SET NOTIFY {ON|OFF} controls whether you're told about new memos on login;
SET LIMIT <n>|NONE sets your mailbox cap (honoured on SEND, NONE = the
network default). New memo_notify/memo_limit account fields + one
MemoPrefsSet event.
2026-07-16 00:29:33 +00:00
b5e08c33cf
MemoServ: add IGNORE
All checks were successful
CI / check (push) Successful in 3m34s
IGNORE ADD/DEL/LIST manages a per-account memo-ignore list; a memo from
an ignored account is silently dropped, and the sender is still told it
was sent so the ignore isn't revealed. New memo_ignore account field +
events, and a check in SEND.
2026-07-16 00:10:34 +00:00
53d3d63464
NickServ GETEMAIL + ChanServ SET RESTRICTED
All checks were successful
CI / check (push) Successful in 3m51s
GETEMAIL (auspex) lists accounts whose email matches a glob. SET
RESTRICTED makes a channel kick anyone without access (or oper) as they
join, mirroring the SECUREOPS on-join enforcement.
2026-07-15 19:25:41 +00:00
be4860c9a8
OperServ: add FORBID (nick/channel/email registration bans)
All checks were successful
CI / check (push) Successful in 3m56s
FORBID ADD/DEL/LIST bans a NICK, CHAN, or EMAIL glob from being
registered — a network-wide policy that gossips like an AKILL and every
node enforces. Nick registration is blocked in pre_register_check (both
NickServ REGISTER and the account-registration relay), and channel
registration in ChanServ REGISTER. New Forbid store + events, mirroring
the akill subsystem.
2026-07-15 19:14:07 +00:00
ac50af92fc
ChanServ: add SET SUCCESSOR with founder inheritance
All checks were successful
CI / check (push) Successful in 3m59s
SET <#channel> SUCCESSOR <account>|OFF names an account that inherits the
channel if the founder's account is dropped or expires, instead of the
channel being released. Adds a ChannelInfo.successor field + event, and
centralises the founder-gone channel release into a single
release_founded_channels store method used by DROP, expiry, and the
gossip account-gone path so all three transfer consistently.
2026-07-15 18:58:08 +00:00
21efbd32c1
NickServ: add UPDATE and LIST
All checks were successful
CI / check (push) Successful in 3m46s
UPDATE re-applies your auto-joins and vhost and re-checks for waiting
memos without re-identifying. LIST (auspex-gated) shows registered
accounts matching a glob, capped at 100. Adds an accounts_matching store
method.
2026-07-15 18:24:52 +00:00
98ecbca414
MemoServ: add CANCEL, CHECK, and INFO
All checks were successful
CI / check (push) Successful in 3m49s
CANCEL recalls the sender's most recent unread memo to a target (one the
recipient already read can't be recalled); CHECK reports whether the last
memo you sent someone has been read; INFO summarises your mailbox. Adds
memo_cancel/memo_check store methods and shares MAX_MEMOS from the module
root instead of duplicating it in send.rs.
2026-07-15 18:09:33 +00:00
994e8c7347
Make the SCRAM verifier the sole password credential; finish the account-registration relay
All checks were successful
CI / check (push) Successful in 4m1s
Credentials: the SCRAM-SHA-256 verifier is now the only password
credential. PLAIN and IDENTIFY verify the plaintext against it via
scram::verify_plain, exactly as a SCRAM login proves knowledge of it, so
an account provisioned from a verifier alone (external authority) works
over every mechanism, not only SCRAM. Removed the redundant argon2 hash
entirely: the field, the Credentials member, the AccountPasswordSet
field, hash_password/verify_password, and the argon2 crate. SASL SCRAM
already forces a PBKDF2 verifier into the store, so the hash never raised
the at-rest floor. OS RNG now comes from rand_core.

Registration: finished draft/account-registration over the ircd relay.
VERIFY confirms the emailed code, RESEND reissues it, STATUS reports
state, and REGISTER now emails the code and replies verification_required
on the relay path too, not only through NickServ.
2026-07-15 15:47:41 +00:00
e551a01cbf
Harden for production: fsync writes, deployment + backup tooling
All checks were successful
CI / check (push) Successful in 3m55s
The event log is the account/channel authority, so make it durable:
persist() now fsyncs every committed event before reporting success, and
a serialisation failure is an error instead of a silently-dropped blank
line. Add a hardened systemd unit (scripts/echo.service), an atomic
gzip backup script with retention (scripts/backup.sh), and a Deployment
wiki page (install, durability, backup/restore, upgrade, monitoring).
2026-07-15 11:45:50 +00:00
d9c0878b00
Log store write failures at the source; share the oper guard
The store's map_err(|_| Internal) discarded the real IO error, so a
disk-full or permissions failure was invisible; log it once in the log
append path where it happens. Add echo_api::require_oper (parameterised
by the required privilege) and route the three copied per-service guards
through it, keeping each service's own wording and policy.
2026-07-14 23:19:00 +00:00
993f5b2eea
Rename the project to Echo (was fedserv)
Rebrand the daemon and workspace: the binary is now `echo`, every crate
is `echo-*` (`echo-api` and the module crates), Rust imports use
`echo_*`, the gRPC proto is `proto/echo.proto` with package `echo.v1`,
and the log filter, gossip peer-name default, and on-disk store default
(`echo.db.jsonl`) follow. Docs updated throughout. No behaviour change;
crate directories and the config schema are untouched.
2026-07-14 15:24:41 +00:00
0c7976e80d
Reorganise the store into a domain-split module tree
engine/db.rs had grown past 4k lines. Split the single Db type's
operations into sibling files by domain — account, channel, network,
the event log, and the module-facing store facade — while all type
definitions, the log plumbing, and shared helpers stay in db/mod.rs.
Pure reorganisation: no behaviour change, tests unchanged and green.
2026-07-14 13:57:05 +00:00