Commit graph

52 commits

Author SHA1 Message Date
76a7162227
OperServ: per-IP session limiting with exceptions
The connection that puts an IP over its allowance is killed on connect.
The ircd's per-user IP (UID param 7) is now plumbed through UserConnect and
tracked per-IP in the live network view (incremented on connect, released
on quit).

- [session] default_limit sets the base allowance (0/absent = off).
- OperServ SESSION LIST <min> / VIEW <ip> inspect live counts.
- OperServ EXCEPTION ADD <ip-mask> <limit> [reason] / DEL / LIST override
  the limit per IP-mask (limit 0 = unlimited); event-sourced so they
  federate and survive restart, matched most-permissive-wins.

All admin-gated.
2026-07-14 01:46:09 +00:00
a67409e0d3
OperServ: OPER — runtime operator management
OPER ADD <account> <priv[,priv]> / DEL <account> / LIST grants or revokes
services-operator privileges (auspex, suspend, admin) at runtime, without
editing config and restarting. Runtime grants are event-sourced (Global,
so they federate and survive restart) and UNIONED with the declarative
[[oper]] config at the engine, so either source makes an account an
operator; a config oper can't be revoked here. Admin-only.

Factored the privilege-name parsing into Privs::from_names (+ names/union)
so config loading and runtime grants share one definition.
2026-07-14 01:33:30 +00:00
e45eab2cd6
OperServ: NEWS — logon and oper announcements
NEWS ADD <LOGON|OPER> <text> / DEL <LOGON|OPER> <number> / LIST. Logon news
is shown to every user as they connect; oper news is shown to an operator
when they log in. Admin-only to manage. Each item carries a stable id
assigned at add time and embedded in the log, so deletion is order-
independent under replay (DEL by number resolves to that id).

Enabling groundwork: the replicated network-wide lists (bans, now news)
are consolidated into one NetData struct threaded through apply() as a
single parameter — this replaces the akill parameter rather than adding
one, so the fold stays within its argument budget as the lists grow.
2026-07-14 01:26:12 +00:00
51d4ebf789
OperServ: INFO staff notes on accounts and channels
INFO ADD <target> <note> / DEL <target> / <target> attaches a staff note
to an account or channel (a #name is a channel, else an account). The note
is shown in that service's INFO — but to operators only (Priv::Auspex),
never to the account's own owner. Admin-only to set.

Follows the typed-field-on-the-entity pattern: an oper_note field folded
through AccountOperNoteSet (Global) / ChannelOperNoteSet (Local) events,
read via dedicated Store getters so it stays out of the public views and
the directory feed.
2026-07-14 01:15:00 +00:00
ab5dc2e931
OperServ: IGNORE — silence a user across all services
IGNORE ADD [+expiry] <mask> [reason] / DEL <mask> / LIST. While a user
matches, the dispatch choke-point drops their service commands silently.
A mask with an @ matches nick!*@host (ident isn't tracked); a bare mask
matches the nick; matching is case-insensitive with lazy expiry. Admin-
only, and an operator is never ignored (so staff can't lock themselves
out). Node-local and in-memory, like the auth and request throttles — a
fast transient moderation tool, not persisted or federated.
2026-07-14 01:00:05 +00:00
c5d93f29c1
OperServ: MODE override and KICK
Two channel operator tools:

- MODE <#chan> <modes> [params] forces a channel mode change (TS 1, so it
  applies regardless of the current TS). A status-mode target may be given
  as a nick — it's resolved to the uid the ircd's FMODE expects.
- KICK <#chan> <nick> [reason] removes a user, sourced from OperServ and
  attributed to the operator.

Both admin-gated. To resolve status-mode params, channel-mode parameter
arity became one shared helper (chanmode_takes_param + STATUS_MODES in the
api): the ircd module now delegates to it instead of keeping its own copy,
so burst-parsing and MODE-building can never drift.
2026-07-14 00:54:29 +00:00
930198b826
Expand OperServ: SQLINE nick bans, GLOBAL, and KILL
Generalise the network-ban store to carry the ircd X-line kind (defaulted
to the user@host G-line for existing records) so one event-sourced,
lazily-expiring, burst-reasserted list backs every ban type, and drive
ADD/DEL/LIST from one implementation per kind:

- AKILL stays the user@host G-line; SQLINE is a nick Q-line, its mask
  validated as a nick glob (an @ is refused so an AKILL-shaped mask can't
  become an over-broad nick ban). Both are keyed by (kind, mask).
- GLOBAL <message> announces to every user via a $* server-glob notice.
- KILL <nick> [reason] disconnects a user, attributed to the operator.

All four are admin-gated, and OperServ itself stays invisible to non-opers.
Bans and kills go out as typed actions (AddLine/DelLine/KillUser) the ircd
link serialises to ADDLINE/DELLINE/KILL.
2026-07-14 00:45:52 +00:00
d2c1d076fb
Add OperServ with AKILL network bans
The first operator module. AKILL manages network bans as a typed,
event-sourced list with lazy expiry, mirroring the patterns the other
modules use:

- AKILL ADD [+expiry] <user@host> <reason> stores the ban and drives an
  ircd G-line (applied to matching users already online and to future
  connections); AKILL DEL <mask|number> lifts it; AKILL LIST [pattern]
  shows the live list, numbered. Admin-only.
- Bans are Global events, so every node holds the list and re-asserts each
  one at burst with its remaining duration. Expired bans are hidden lazily
  and dropped at compaction.
- Masks are normalised to user@host (a nick! prefix is stripped, both
  sides lowercased) and an all-wildcard mask is refused.

Email (send_email) is already reachable from the api via ServiceCtx, and
adds AddLine/DelLine to the action vocabulary for any future X-line kinds.
2026-07-14 00:12:29 +00:00
253d4c2a2d
Add lazy inactivity-expiry for accounts and channels
Accounts and channels now carry a last-activity stamp and are dropped
once inactive past a configured threshold. A login stamps the account, a
join stamps the channel — both coalesced to at most one log entry per day
so routine traffic doesn't bloat the log, and stamps replicate so
activity on any node keeps a record alive network-wide.

Expiry is lazy in the proven style: a periodic pass computes it from the
stored stamps with no per-record timer, reusing the same relation the
vhost and suspension expiries use. The sweep spares operator accounts,
accounts with a live session, occupied channels, and anything an operator
has pinned with the new NOEXPIRE command (NickServ NOEXPIRE <account>,
ChanServ NOEXPIRE <#channel>, both Priv::Admin). Each expiry is announced
to the audit channel and clears the channel's +r.

Configured under [expire] (accounts_days / channels_days); a zero or
omitted field leaves that kind never expiring, and omitting the section
disables expiry entirely.
2026-07-14 00:02:36 +00:00
31d23cf6b3
HostServ: normalise vhosts + enforce uniqueness
Canonicalise a requested vhost the way the ircd displays it (a disallowed
underscore becomes a hyphen) before storing and applying, so what we keep
matches what shows on the network. A vhost can no longer be assigned to two
accounts: prepare_vhost normalises, validates, and checks vhost_owner at
every assignment point (SET/REQUEST/ACTIVATE/TAKE/DEFAULT), so two inputs
that collapse to the same host are caught as one.

Approach recommended by siniStar (the ns_nethost normalisation logic).
2026-07-13 23:05:41 +00:00
271e06fa77
HostServ: temporary vhosts (expiry)
SET <account> <host> [duration] assigns a vhost that lapses after the
duration (e.g. 30d); a Vhost carries an optional expiry and the store hides
an expired one lazily (like a suspension), so it stops applying on identify/
ON without a sweep. LIST flags temporary vhosts.
2026-07-13 22:53:04 +00:00
87bad7fbcc
HostServ: throttle vhost requests
A member may request a vhost at most once a minute; a request sooner is
refused with the wait time. In-memory per-account (not event-logged), the
same shape as the identify brute-force throttle.
2026-07-13 22:47:02 +00:00
6a8e02f004
HostServ: forbidden-vhost patterns + auto-vhost template
FORBID <regex> / FORBIDLIST / FORBIDDEL let operators block impersonating
user requests (e.g. (?i)(admin|staff)); REQUEST refuses a matching host.
Matching reuses the linear-time RegexSet, so untrusted patterns are safe.

TEMPLATE <$account...> sets a network auto-vhost pattern; DEFAULT gives a
user $account.users.example with their sanitised account name. HostServ's
node config (offers/forbidden/template) is consolidated into one HostConfig
threaded through the log replay.
2026-07-13 22:42:25 +00:00
2c32dcdc63
HostServ: vhost OFFER menu, plus SETALL/DELALL/GROUP
A curated self-serve menu: operators OFFER <host> / OFFERDEL <n>, anyone
sees OFFERLIST, and a user picks one with TAKE <n> (assigned and applied
at once). Offers are a global event-sourced list (VhostOfferAdded/Removed,
threaded through apply like bots). SETALL/DELALL alias SET/DEL and GROUP
is a no-op reassurance, since the per-account vhost already covers every
grouped nick.
2026-07-13 22:11:58 +00:00
898461d1c2
HostServ: ident@host vhosts (CHGIDENT)
A vhost may be ident@host, not just host: apply_vhost splits it and sends
CHGIDENT for the ident plus CHGHOST for the host. valid_vhost validates
the optional ident part; ON/SET/ACTIVATE/identify all route through
apply_vhost, so a bare host still works unchanged. New SetIdent action.
2026-07-13 22:04:16 +00:00
1edf250952
HostServ: vhost request/approval flow
REQUEST <host> records a pending vhost (typed Option<PendingVhost> on the
account, VhostRequested/VhostRequestCleared events); operators review with
WAITING and either ACTIVATE (assign + apply to online sessions) or REJECT
(clear it). Host validation and the operator gate are shared with SET.
2026-07-13 21:30:58 +00:00
1227e26b95
Add HostServ: virtual hosts
New HostServ pseudo-service assigns and applies vhosts. A vhost is a typed
Option<Vhost>{host,setter,ts} on the account (VhostSet/VhostDeleted events),
applied to the displayed host on identify and re-applied by ON; OFF restores
the normal host for the session. Operators SET/DEL (applied at once to
online sessions) and LIST; hosts are validated as dot-separated labels.

New SetHost NetAction -> InspIRCd ENCAP <target-sid> CHGHOST <uid> <host>.
2026-07-13 21:26:38 +00:00
04f927d3db
Promote BotServ BOTSTATS to a StatServ pseudo-service
Stats now have their own service. StatServ answers /msg StatServ <#channel>
(per-channel activity — lines + top talkers, founder-or-admin) and
/msg StatServ SERVER (the shared cross-service counter registry + gauges,
operators only). The shared counter map moved from the engine onto the
network view so both StatServ (via NetView::stat_counters) and the gRPC
Stats API read one source. BotServ's BOTSTATS command is removed; the
per-channel activity is still recorded in the dispatch loop.
2026-07-13 20:04:03 +00:00
ad61addb84
BotServ: community !votekick / !voteban
SET <#channel> VOTEKICK <n> enables in-channel voting: members type
!votekick <nick> (or !voteban), each voter counts once, and when n votes
are reached within the window the assigned bot kicks (or bans then kicks)
the target. Votes are ephemeral and lapse after two minutes. Crowd
moderation with no op online.
2026-07-13 19:32:25 +00:00
374748b1f9
BotServ: richer TRIGGERs — capture groups + cooldown
Responses can now use $1..$9 to substitute regex capture groups (e.g.
ADD hi (\w+)|Hi $1!), and an optional trailing |<secs> sets a per-trigger
cooldown so a trigger can't be spammed. The cache compiles each pattern to
its own Regex (for captures) and tracks last-fired for the cooldown.
2026-07-13 19:25:35 +00:00
766ef6ca47
BotServ: BOTSTATS per-channel activity
BOTSTATS <#channel> reports the lines the bot has seen this session and
the top talkers. Tracked live in the network view (bounded per-nick map),
recorded for bot-assigned channels only, and also fed to the shared stats
registry as botserv.messages.
2026-07-13 19:20:52 +00:00
d4e2c905f2
Shared stats registry + gRPC Stats API
A single namespaced counter map on the engine that every service reports
into: ServiceCtx::count(key) is folded in after each command, the engine
bumps its own events (botserv.kick/ban/warn/greet/trigger), and a
per-service command counter (nickserv.command, chanserv.command, …) is
tallied in dispatch. stats_snapshot() merges those with live gauges from
the store (accounts/channels/bots/opers total).

Exposed over gRPC as a new Stats service (Get -> map<string,uint64>),
authorized by the same bearer token as Directory/Accounts — so any
consumer (a dashboard, Django) reads every module's stats through one
pipe. New service is additive to the proto; existing RPCs untouched.
2026-07-13 19:13:31 +00:00
5d1bfb3144
BotServ: DONTKICKVOICES, plus neutral code comments
KICK <#channel> DONTKICKVOICES <on|off> exempts voiced users from the
kickers. The network view now tracks +v the same way it tracks +o: a new
ChannelVoice event is parsed from FMODE +v/-v and from +v join prefixes,
and set_voice/is_voiced mirror set_op/is_op (voice is cleared on part and
quit). scan_ops generalised to scan_status for any status letter.

Also scrubbed third-party project name-drops from code comments.
2026-07-13 18:19:37 +00:00
929a4cdd92
BotServ: TRIGGER — regex auto-responses
TRIGGER <#channel> ADD <regex>|<response> | DEL <num> | LIST | CLEAR: when
a channel line matches <regex>, the assigned bot says <response> ($nick
becomes the speaker's nick). Only the first matching trigger fires, and a
line the bot just kicked for gets no response.

Same engine machinery as badwords: patterns compile into one RegexSet per
channel (linear-time, ReDoS-safe), cached and rebuilt only on a revision
change. Patterns validated + size-limited at add time.
2026-07-13 18:03:46 +00:00
47b1fd351e
BotServ: KICK <#channel> WARN — one warning before the first kick
With WARN on, a user's first kicker offence gets a private warning from
the bot instead of a kick; the next one is kicked (and then TTB can ban).
The warned flag rides the existing per-user chatter state.
2026-07-13 17:57:40 +00:00
c8be5b4e95
BotServ: COPY <#src> <#dst> to clone bot config
Copies a channel's kickers, badword list, greet and nobot settings onto
another channel (founder-or-admin on both). Cheap because the config is a
typed KickerSettings + Vec<String>, not a bag of stringly extensibles.
2026-07-13 17:55:19 +00:00
0edc4d4e87
BotServ: KICK <#channel> TEST <message> dry-run
Reports which content kicker (caps/formatting/badwords) a line would trip,
without kicking anyone — so an op can tune caps% or a badword regex safely.
Reuses the typed KickerSettings::violation() and the shared badword-set
builder (moved to db so the test and the live path share it).
2026-07-13 17:53:13 +00:00
9f5f0ed0d2
BotServ: BOT DEL * mass removal (Anope #315)
BOT DEL * removes every registered bot at once, quitting each pseudo-
client, for cleaning up after a bot spree.
2026-07-13 17:50:50 +00:00
0e90dd8020
BotServ: richer INFO
INFO <#channel> now lists the BotServ options in effect (greets, kickers,
nobot) alongside the assigned bot; INFO <bot> flags a private bot.
2026-07-13 17:38:51 +00:00
5bc41f2e60
BotServ: NOBOT and PRIVATE assignment protections
SET <#channel> NOBOT <on|off> reserves (un)assigning a bot for services
operators (the founder is refused). SET <bot> PRIVATE <on|off> (admin
only) marks a bot operators-only to assign; it's flagged in BOT LIST and
the private flag survives BOT CHANGE. Both gates live in the ASSIGN path.
2026-07-13 17:36:34 +00:00
1cc438f4ed
BotServ: BOT CHANGE
BOT CHANGE <oldnick> <newnick> [user [host [gecos]]] renames or
re-identifies a bot; omitted fields keep the current values. A rename
moves every channel the bot was assigned to onto the new nick. reconcile
now tracks each live bot's identity hash, so a change that keeps the nick
but alters user/host/gecos quits the stale pseudo-client and reintroduces
it (rejoining its channels) — a rename does the same via the registry.
2026-07-13 17:30:27 +00:00
ec3205bf75
BotServ kickers: times-to-ban (TTB) + BANEXPIRE
KICK <#channel> TTB <n> makes the bot ban a user after they've been
kicked n times (by any kicker) instead of only kicking; SET <#channel>
BANEXPIRE <duration|off> controls how long that ban lasts. One clear
per-channel knob rather than Anope's per-kicker ttb array.

The kick count rides in the existing ephemeral per-user chatter state, so
it's dropped when the user parts or quits. On the ban the bot sets +b on
an ideal host mask and, if BANEXPIRE is set, queues an unban that is swept
off the next event — no timer thread. Ban time is injectable for tests.
2026-07-13 17:12:12 +00:00
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