Commit graph

120 commits

Author SHA1 Message Date
ca95184359
BotServ BADWORDS: user-configurable regex kicker
BADWORDS <#channel> ADD|DEL|LIST|CLEAR manages a channel's badword
patterns, each a regular expression, so a channel matches whatever it
wants — an improvement over Anope's fixed ANY/SINGLE/START/END. Enable
with KICK <#channel> BADWORDS ON.

Uses the regex crate: it's finite-automata based with linear-time
matching, so untrusted user patterns can't cause catastrophic
backtracking (ReDoS). A channel's patterns compile into one RegexSet
(single-pass match against all of them), cached in the engine and rebuilt
only when the list's revision changes — so the hot path is one is_match
call, no per-message compilation or allocation. Patterns are validated
(and size-limited) at add time; matching is case-insensitive by default.
2026-07-13 16:16:44 +00:00
43def8ee23
BotServ kickers: FLOOD and REPEAT
KICK <#channel> FLOOD ON [lines [secs]] kicks a user who sends too many
lines too fast (default 6 in 10s); KICK <#channel> REPEAT ON [times]
kicks one who repeats the same line (default 3, case-insensitive).

These are stateful, so the engine keeps per-channel/per-user counters in
an ephemeral map that is never event-logged. Hot-path conscious: a channel
with no kickers costs one lookup and returns; a returning speaker allocates
nothing (get_mut, not entry); repeat comparison is an allocation-free
case-folding 64-bit hash. Counters are dropped when a user parts or quits,
so the map stays bounded. Flood time is injectable for deterministic tests.
2026-07-13 15:18:39 +00:00
ed7da9c713
BotServ kickers: caps + formatting, with DONTKICKOPS
The assigned bot now enforces kickers on channel lines: KICK <#channel>
CAPS ON [min [percent]], and BOLDS/COLORS/UNDERLINES/REVERSES/ITALICS
for control-code formatting. DONTKICKOPS exempts channel operators. A
tripping line is kicked by the bot itself.

Kicker config is a typed KickerSettings struct on the channel (event-
sourced like ChanSettings) with a violation() check; the engine runs it
on every channel PRIVMSG. Factored the founder-or-admin gate into a
shared require_channel_admin helper (assign/set/kick).
2026-07-13 14:54:14 +00:00
ad8041bd9d
BotServ GREET + NickServ SET GREET
Members set a personal greet with NickServ SET GREET; a channel founder
enables display with BotServ SET <#channel> GREET ON. On join, if greets
are enabled and the member has channel access and a non-empty greet, the
assigned bot shows [account] greet in the channel.

Per-account greet is a typed field on Account (AccountGreetSet event, rides
the account snapshot); the per-channel toggle is a new ChanSetting::BotGreet
bool. Greet is public in NickServ INFO.
2026-07-13 14:43:48 +00:00
c7d05a8b77
BotServ: add SAY and ACT
The channel's assigned bot can now speak: SAY <#channel> <text> and ACT
<#channel> <text> (CTCP ACTION), gated on channel-operator access. Bots
are tracked in the network view so uid_by_nick resolves them, and the
message is sourced from the bot's own uid via a new ServiceCtx::privmsg.
2026-07-13 14:15:20 +00:00
b25870e14b
botserv: ASSIGN / UNASSIGN a bot to a channel
A channel gains a typed assigned_bot (event-logged, snapshotted). ASSIGN
<#channel> <bot> / UNASSIGN <#channel> is founder-gated (or admin oper). The
engine reconcile now also diffs channel assignments against tracked bot
memberships: a live bot IJOINs its assigned channels and PARTs unassigned ones
(new ServiceJoin/ServicePart actions), at burst and after commands; a deleted
bot's memberships are dropped. Engine test for join-on-assign / part-on-unassign.
2026-07-13 13:42:14 +00:00
577c05ad2e
memoserv: complete + improve the memo service
Finish MemoServ on the typed, event-logged memo model: Db memo_send/list/read/
del/unread_memos + Store facade, and a new fedserv-memoserv crate (a default
service). Beyond the basics: SEND caps a mailbox (30) so nobody's flooded, READ
takes <num>|NEW|ALL, DEL takes <num>|ALL, LIST previews and flags unread, and
NickServ notifies you of waiting memos on login. Memos persist and federate
(Global scope) like other account data. Also lands NetAction::QuitUser (BotServ
plumbing). Data + delivery/login-notify tests.
2026-07-13 04:50:27 +00:00
651cb683de
botserv: bot registry + BOT ADD/DEL/LIST (first slice)
Adds BotServ as a new module crate (fedserv-botserv) and a persisted bot
registry: a typed Bot{nick,user,host,gecos} threaded through the event log
(new bots map folded in apply, snapshotted, Local scope). BOT ADD/DEL/LIST
gated on the typed Priv::Admin. BotServ is a default service (uid suffix
AAAAAD). Next slices: introduce bots on the network, ASSIGN to channels, and
in-channel fantasy commands. Data + oper-gated engine tests.
2026-07-13 04:33:27 +00:00
cb081a2e95
chanserv: channel SUSPEND / UNSUSPEND (oper-gated)
Freezes a channel gated on Priv::Suspend: a typed Suspension on ChannelInfo
(event-logged, snapshotted, lazy expiry — same shape as the account one, no
Anope Extensible/Checker or expiry timer). While suspended, ChanServ refuses
all management (guards in require_op/require_founder and the founder-inline
SET/DROP/MLOCK), the engine skips Join enforcement (no auto-op/akick/entrymsg),
and suspending kicks everyone out; INFO shows it. parse_duration moved into the
SDK so both SUSPEND commands share it. Data + full-flow tests.
2026-07-13 04:17:17 +00:00
bbbe2c6cb8
nickserv: account SUSPEND / UNSUSPEND (oper-gated)
Freezes an account (data kept, login blocked) gated on the typed Priv::Suspend.
A typed Suspension{by,reason,ts,expires} on the account, folded through the
event log (persists, replays, federates) -- no stringly Extensible/Checker like
Anope. Expiry is evaluated lazily at check-time, so there is no expiry timer.
Blocks IDENTIFY and all SASL mechanisms (via one sasl_login gate); logs out any
live sessions on suspend; the login refusal names who/when/why and when it
lifts. Shown in INFO to owner/auspex. Data + full-flow tests.
2026-07-13 04:01:30 +00:00
f1415bedb2
engine: services-operator privilege foundation
A typed Priv enum (Auspex/Suspend/Admin) and a Copy Privs bitset carried on
Sender, instead of Anope's stringly-typed privilege namespace and its two
separate gating mechanisms. Opers are declared in TOML ([[oper]] account +
privs); the engine loads them and resolves the sender's privileges per command,
so a module just checks from.privs.has(Priv::X). First use: NickServ INFO
reveals hidden fields to an auspex oper. Unblocks SUSPEND, admin LIST, SASET,
GETEMAIL. Priv bitset test + end-to-end auspex engine test.
2026-07-13 03:42:59 +00:00
4600ee062c
chanserv: SET TOPICLOCK and KEEPTOPIC (topic subsystem)
Adds topic tracking: a TopicChange event (parsed from InspIRCd FTOPIC, our own
changes filtered by SID like FMODE) and a stored topic on the channel, folded
through the event log. TOPICLOCK reverts a topic change by a user without
op-level access back to the stored topic; KEEPTOPIC remembers the topic and
restores it when a registered channel is recreated. Both via the typed
ChanSettings path; shown in INFO. Parse + engine tests cover both.
2026-07-13 03:30:04 +00:00
d2c6448c6b
chanserv: SET SECUREOPS (strip ops from users without access)
When SECUREOPS is on, a user who gains channel-operator status without op-level
access is immediately deopped. Enforced in the engine's ChannelOp hook (manual
FMODE +o) and the Join hook (FJOIN prefix ops), sourced from ChanServ. Same
typed-settings path; shown in INFO; engine test covers on and off.
2026-07-13 03:14:43 +00:00
3bf6b2be77
chanserv: SET PEACE (block acting against equal-or-higher access)
PEACE stops a channel op from using ChanServ KICK/BAN/DEOP/DEVOICE against
someone whose access rank (founder>op>voice) is equal to or above their own.
Adds a typed access_rank on ChannelView and a peace_blocks guard in those
commands. Same typed-settings path as SIGNKICK/PRIVATE; shown in INFO.
2026-07-13 03:06:58 +00:00
75019c867a
chanserv: SET SIGNKICK and PRIVATE (typed channel settings)
Introduce a typed ChanSettings struct + ChanSetting enum for ChanServ's on/off
options instead of a bag of string flags, so a new option is one field and the
compiler proves every site handles it. First two, both functional: SIGNKICK
attributes ChanServ kicks with who requested them; PRIVATE hides the channel
from LIST. Folded through the event log like the other channel state (node-local,
no gossip), shown in INFO. One ChannelSettingsSet event, one Store method.
2026-07-13 02:54:46 +00:00
f388e3d650
nickserv/security: password policy, auth throttle, unguessable codes
Three brute-force / weak-secret gaps closed:
- REGISTER and SET PASSWORD now enforce a length range and reject a password
  equal to the nick (Anope's minpasslen/not-nick rules), with tests.
- IDENTIFY is throttled per account: a few free tries then an exponential
  backoff that clears on success, so a password can't be ground down. SASL had
  a limiter; interactive login had none.
- Emailed confirm/reset codes go from 6 digits to 8 base32 chars (~40 bits) and
  are burned after a few wrong guesses, closing a reset-code takeover window.
Throttle and code state are node-local and in-memory (not in the event log).
2026-07-13 02:28:47 +00:00
75ba9b35b4
nickserv: AJOIN auto-join list
Adds the account auto-join list (AJOIN ADD/DEL/LIST): NickServ SVSJOINs you
into your listed channels each time you identify. The list is a typed
Vec<AjoinEntry> on the account, folded through the event log like the cert and
akick lists (persists and replays; no separate store), reached by modules
through the Store trait. New ForceJoin action serialises to InspIRCd SVSJOIN.
Shown in INFO. Operates on the caller's own account; the oper form for other
users waits on a privilege system.
2026-07-13 02:09:03 +00:00
596630df53
modules: split services and the ircd link into external crates
nickserv, chanserv and the InspIRCd protocol move out of the binary into their
own workspace crates (fedserv-nickserv, fedserv-chanserv, fedserv-inspircd),
each depending only on fedserv-api. human_time and the branded account emails
move into the SDK crate so a module needs nothing from core; the engine keeps
its own inherent methods and builds emails via fedserv-api too. The bin now
constructs each module from its crate instead of an in-tree #[path] include.
Proves the SDK is self-sufficient: a third-party module is the same shape.
2026-07-13 01:33:56 +00:00
8ed1a9ab70
modules: capability-scoped Store/NetView boundary (Tier 2)
Services no longer receive the concrete account store. on_command now takes
&mut dyn Store + &dyn NetView (both defined in fedserv-api); the engine's Db
and Network implement them. Reads hand back plain views (AccountView,
ChannelView, ...) carrying only non-secret fields, so the event log, gossip,
and credential material (password hash, SCRAM verifiers) are unreachable from
a module. All command modules ported; behaviour unchanged.
2026-07-13 01:04:07 +00:00
931b8727e9
modules: extract fedserv-api leaf crate (protocol + service vocabulary)
Move NetEvent/NetAction/RegReply/Protocol and Sender/ServiceCtx into a
dependency-free fedserv-api crate that a module builds against. Core keeps
referring to them through re-export shims at crate::proto and
crate::engine::service, so no consumer changes. Groundwork for external
service and protocol modules.
2026-07-13 00:36:42 +00:00