Commit graph

306 commits

Author SHA1 Message Date
92383cee0e
Split DiceServ command out of lib.rs
The one command handler (ROLL/EXROLL/CALC/EXCALC all route to it) and
its formatting helpers move to roll.rs; lib.rs keeps the dispatcher, the
expression-evaluator module, and its tests. No behaviour change.
2026-07-14 14:49:42 +00:00
a29becb8b9
Split ReportServ into one file per command
REPORT/LIST/VIEW/CLOSE/DEL each move to their own file; the shared oper
guard stays in lib.rs beside the dispatcher. No behaviour change.
2026-07-14 14:48:12 +00:00
0ba736615b
Split InfoServ into one file per command
POST/DEL/LIST each move to their own file (each still parameterised by
bulletin kind, so OPOST/ODEL/OLIST route to the same handler); the kind
constants stay in lib.rs beside the dispatcher. No behaviour change.
2026-07-14 14:46:56 +00:00
368e6ba090
Split HelpServ into one file per command
REQUEST/CANCEL/LIST/VIEW/TAKE/NEXT/CLOSE each move to their own file;
the oper guard and the shared claim helper stay in lib.rs beside the
dispatcher. No behaviour change.
2026-07-14 14:45:22 +00:00
1916e5fce5
Split GroupServ into one file per command
REGISTER/DROP/INFO/LIST/ADD/DEL/FLAGS each move to their own file; the
two shared guards (login check, can-manage) stay in lib.rs beside the
dispatcher. No behaviour change.
2026-07-14 14:43:18 +00:00
feba6700cb
Split ChanFix into one file per command
lib.rs kept only the struct and the dispatcher; SCORES and CHANFIX now
each live in their own file (scores.rs, fix.rs), matching every other
service. No behaviour change.
2026-07-14 14:40:43 +00:00
a803177f8b
Nest the ircd link under modules/protocol/
The protocol link is a different kind of module from the service
pseudo-clients, so give it its own home: modules/inspircd ->
modules/protocol/inspircd. A future ircd link is a new crate alongside
it. Workspace member, dependency path, the crate's own api path, and the
docs are updated to match. No code change.
2026-07-14 14:28:18 +00:00
ad2a623120
Group the module crates under modules/
The service pseudo-clients and the ircd protocol link sat flat at the
repo root, mixed in with the daemon core and the SDK. Move them all
under modules/ so the tree separates concerns cleanly: the daemon in
src/, the SDK every module links against in api/, and the loadable
modules — the pseudo-clients plus the protocol link — in modules/.

Workspace members, the daemon's per-crate dependency paths, and each
module's api path are updated to match; the docs follow. No code change.
2026-07-14 14:19:43 +00:00
6f76f9722c
Reorganise the engine into a concern-split module tree
engine/mod.rs had grown past 5k lines, most of it one test module and
a few large method clusters. Lift the tests into engine/tests.rs, and
move the cohesive method groups into sibling files — sasl, kicker
(chat policing), register (account-authority relay), and dispatch
(service routing and fantasy). The Engine type, its master event
handler, shared helpers, and the small state structs stay in mod.rs;
each sibling adds its own impl Engine block. Pure reorganisation: no
behaviour change, tests unchanged and green.
2026-07-14 14:05:56 +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
5e6bc4334c
Fantasy !roll: DiceServ answers, the bot speaks it in-channel
Extends the fantasy router so a dice command (!roll/!calc/!exroll/!excalc)
in a bot channel routes to DiceServ (if loaded) instead of ChanServ, and
its reply is turned into the assigned bot speaking the result to the
channel — so a tabletop game rolls in the open, not in a private notice.
Everything else still routes to ChanServ. Factored the dispatch into
service_uid()/route_to() helpers shared by both branches.
2026-07-14 07:38:58 +00:00
a47dd6bda3
Add HelpServ: a help-desk ticket queue
Users open tickets with REQUEST (or HELPME); the queue is event-sourced
and, because a request is an event, the audit feed announces it to staff
and records it in the searchable log — so OperServ LOGSEARCH finds a ticket
by any word, the same trail as reports. A user can CANCEL their own open
ticket. Operators work the queue: LIST [ALL], VIEW, TAKE (claim), NEXT
(claim the oldest unassigned), CLOSE. Filing is rate-limited. Opt-in.

That completes the six third-party modules — all interconnected: they
share the account/channel/oper identity, the event-sourced store, and the
one audit/incident trail.
2026-07-14 04:39:45 +00:00
06227e73da
Add ChanFix: recover opless channels from op-time scores
The engine samples live op state on a short cadence, scoring op-time per
identity (account, else host) on the network view — a net +1 per pass
while opped, -1 while idle, so trusted regulars rise and stale ops fade.
ChanFix (operator-only) reads those scores: SCORES <#chan> shows the
standings, CHANFIX <#chan> reops the present regulars scoring above a
share of the top score, once the channel is opless enough and has real
history. It defers to ChanServ — a registered channel is refused. Opt-in.

C++ note: the original persists scores to the services DB and runs its own
timers; here scoring rides the existing periodic loop and lives on the
ephemeral network view, so it's node-local and rebuilds itself.
2026-07-14 04:33:48 +00:00
09be99785e
Add GroupServ, with groups that grant channel access
User groups (!name) — a founder plus member accounts, each with group-
access flags (the shared flag primitive: F founder, f manage, i invite,
c chan-access, s set, m memo). REGISTER/DROP/INFO/LIST/ADD/DEL/FLAGS;
event-sourced and federated like accounts.

The flagship interconnection: a channel access entry may name a !group, so
every member inherits that channel access. Resolution is group-aware — a
new db.channel_caps() unions a user's direct access with any group they
belong to, and the engine's join handler auto-ops/voices through it. So
"ChanServ FLAGS #chan !team +o" auto-ops every member of !team, and
removing someone from the group revokes it. Opt-in.
2026-07-14 04:26:07 +00:00
c7a4d5be27
Add a shared channel-access FLAGS model
A granular access system, replacing the op/voice-only levels with per-user
flag letters (f full, o auto-op, O op, h auto-halfop, v auto-voice, t
topic, i invite, a modify-access, s settings, g greet). One shared
primitive in the api — level_caps() resolves a stored level to capabilities
(recognising the legacy op/voice/founder presets so nothing breaks) and
apply_flags() applies +/- deltas — so join_mode/is_op/access_rank all
route through it and a flag-granted +o auto-ops exactly like a legacy op
entry.

ChanServ FLAGS <#chan> [account [+/-flags]] lists or edits access;
changing needs the founder or the 'a' flag. This is the foundation
GroupServ will reuse for group membership flags.
2026-07-14 04:15:28 +00:00
5b023c22a4
Add ReportServ, wired into the audit trail
Any user can REPORT <nick|#channel> <reason>; filing is rate-limited per
reporter. Reports are an event-sourced, federated queue operators review
with LIST / VIEW / CLOSE / DEL. The interconnection: because a filed report
is an event, the engine's audit feed already announces it to the staff
channel and records it in the searchable action log — so OperServ
LOGSEARCH finds a report by any word in it, tying reports into the same
trail as bans, kicks, and everything else. Opt-in (add "reportserv").
2026-07-14 04:02:51 +00:00
8d495a948e
Add InfoServ, unifying network bulletins onto the news store
InfoServ becomes the dedicated home for network information bulletins,
backed by the same event-sourced news store OperServ NEWS used — one place
to post, not two. POST/LIST/DEL manage public bulletins (shown to every
user on connect); OPOST/OLIST/ODEL the oper ones (shown to operators on
login). Anyone may LIST the public bulletins; posting/removing is admin-
only. The engine's existing connect/login display hooks render them
unchanged. The OperServ NEWS command is retired accordingly (the news
events and the display path stay). Default service.
2026-07-14 03:56:09 +00:00
1e8a5eab36
Add DiceServ: dice-roll and math-expression service
A stateless service for tabletop games over IRC. ROLL/CALC evaluate a dice
+ math expression (ROLL rounds to a whole number, CALC keeps decimals);
EXROLL/EXCALC also show each die. Supports NdM / dM / d% dice, + - * / ^ %,
parentheses, implicit multiplication, unary minus, pi/e, a set of functions
(sqrt, floor, sin, min, …), and N~expr to repeat a roll.

Modernises the C++ original: it ships its own Mersenne-Twister RNG in C
and parses via shunting-yard with a variadic-argument hack; this is a
typed AST from a small recursive-descent parser plus the rand crate —
shorter, and the dice/sides/total caps make abuse (999999d6) a clean error
instead of a hang. Opt-in (add "diceserv" to [modules].services).
2026-07-14 03:46:34 +00:00
3f8649414f
gRPC Accounts: Provision from pre-derived SCRAM verifiers
Adds Accounts.Provision(name, scram256, scram512, email) so an external
authority that stores verifiers rather than passwords (a website) can bulk-
backfill accounts it never had the plaintext for. It creates the account
with the given SCRAM verifiers — SCRAM and keycard login work immediately;
typed-password login stays disabled (empty password_hash) until a real
password lands via Register/SetPassword. Refuses an existing account, so a
re-run can't clobber a fully-credentialed one. scram512 may be empty
(SCRAM-SHA-512 then falls back to 256).
2026-07-14 03:33:17 +00:00
fba91b4d62
Add opt-in external-account mode (delegate identity to the website)
By default fedserv owns accounts itself — nothing changes, no external
service required. Set [auth] external = true and an outside authority (the
website) owns identity instead: NickServ REGISTER / DROP / SET
PASSWORD|EMAIL / RESETPASS / CONFIRM / CERT / GROUP and the IRCv3
registration relay are all refused ("managed on the website"), so IRC
can't mint or change a second identity. Login is unchanged — fedserv still
authenticates locally against the accounts the authority pushes in via the
existing gRPC Accounts API, so lookups stay in-memory fast.

fedserv keeps owning all IRC-domain data (channels, access, vhosts, bans,
memos) keyed by the account name in both modes. One config bool, off by
default, so standalone deployments are unaffected.
2026-07-14 03:12:59 +00:00
4c899d80b0
OperServ: DEFCON network defence levels
DEFCON [1-5] reads or sets a network-wide defence posture, each level
adding a restriction the engine and services enforce:
- 4: channel registrations frozen (ChanServ REGISTER)
- 3: all registrations frozen (the pre_register_check choke-point, covering
     NickServ and the IRCv3 relay, plus channels)
- 2: sessions additionally capped to one per host
- 1: full lockdown — new connections are turned away on connect

Setting a level announces it to the whole network. Node-local level on the
db so services can read the derived freezes; admin-only.
2026-07-14 02:35:09 +00:00
85d01b3ebf
OperServ: temporary OPER grants that auto-expire
OPER ADD <account> <priv[,priv]> [+duration] now takes an optional window,
after which the grant lazily stops conferring privileges — no timer, the
oper_privs merge just hides an expired grant (evaluated on the engine's
clock so it's testable). Permanent grants are unchanged; LIST flags the
temporary ones and hides expired entries. Runtime opers now carry an
OperGrant{privs, expires} instead of a bare priv list.
2026-07-14 02:29:03 +00:00
90824513a7
OperServ: CBAN — ban a channel name
CBAN ADD <#channel-glob> <reason> / DEL / LIST blocks users from creating
or joining a matching channel (m_cban is loaded). Another kind ("CBAN")
in the generalized X-line handler with a channel-mask normalizer; the
too-wide guard now ignores a leading channel prefix so #* is refused.
2026-07-14 02:24:13 +00:00
a7aacf347e
OperServ: LOGSEARCH — search the action log
LOGSEARCH [pattern] searches the incident log newest-first: match a bare
id (as stamped into a kick reason), or any word from the summary; no
argument lists the most recent. Auspex-gated (read-only investigation).

To make it comprehensive, the command audit trail now records every
notable action to the same incident log (not just to the audit channel,
and regardless of whether one is configured) — so registrations, drops,
akills, suspensions, vhosts, notes, and the like are all findable
alongside the kicks and kills stamped at the removal choke-point.
2026-07-14 02:16:59 +00:00
dc2331f439
Loosen two exact kick-reason assertions for the incident-id suffix
The reason now carries a trailing [#id]; match the prefix instead of the
whole string.
2026-07-14 02:12:27 +00:00
d2a8d0ef05
Stamp a traceable incident id into every kick and kill
A single choke-point in handle() gives each outgoing Kick / KillUser a
short id, appends it to the reason the user sees ([#3F]), and records a
searchable summary in a bounded per-node incident ring on the live network
view. So a bot flood-kick, a fantasy kick, a votekick, and an operator
KICK are all logged the same way, and the id in a kick reason ties back to
the log entry. Exposed via NetView::search_incidents (id-exact or summary
substring, newest first) for the LOGSEARCH command to come.
2026-07-14 02:09:45 +00:00
1f7591778f
OperServ: SHUN and CHANKILL
- SHUN <user@host> silences a matching user network-wide without
  disconnecting them — another kind ("SHUN") in the generalized X-line
  handler (m_shun is loaded), same event-sourced list and lazy expiry as
  AKILL/SQLINE/SNLINE.
- CHANKILL <#channel> [reason] AKILLs every distinct member host in one
  shot to clear a spam or attack channel; the G-lines the ircd applies
  also kill the sessions. Deduped by host, and never bans the operator who
  ran it. Both admin-only.
2026-07-14 02:00:17 +00:00
1daebc94a5
OperServ: JUPE — hold a server name against a rogue link
JUPE <server.name> [reason] introduces a fake server holding the name so
the real one can't link; JUPE DEL <name> squits it; JUPE LIST shows them.
Node-local (the introducing node owns the jupe, so it isn't federated —
that would collide SIDs across nodes) and re-asserted at burst. A fake
server id is allocated per jupe. New JupeServer/Squit actions serialise to
the mid-link SERVER introduction and SQUIT. Admin-only.
2026-07-14 01:51:07 +00:00
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
78ad909706
OperServ: SNLINE realname bans
SNLINE ADD [+expiry] <realname-regex> <reason> / DEL / LIST — a realname
R-line the ircd matches against connecting users' realnames. It's just
another kind ("R") in the generalized X-line handler: same event-sourced
list, lazy expiry, and burst re-assertion as AKILL/SQLINE, keyed by
(kind, mask). The mask is kept verbatim (a single-token regex; use . or
\\s for spaces) and an all-wildcard pattern is refused. STATS now counts
it too.
2026-07-14 01:36:08 +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
8ccc9fa8ea
OperServ: SVSNICK and SVSJOIN
Force a user's nick change or channel join, reusing the ForceNick and
ForceJoin actions the daemon already relies on (guest renames, auto-join)
so the ircd support is proven. SVSNICK validates the new nick; SVSJOIN
takes an optional key. Both admin-gated and resolve the target via
NetView.
2026-07-14 01:08:46 +00:00
f9aea30f81
OperServ: STATS enforcement overview
STATS reports the live counts OperServ holds — AKILL and SQLINE bans by
kind, and services ignores — as a quick at-a-glance check. Read-only, so
any operator can run it.
2026-07-14 01:02:14 +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
57e104e510
Email owners before inactivity-expiry drops their account or channel
When [expire] warn_days is set and email is configured, the expiry sweep
now emails a heads-up to owners entering the warning window (inactive past
the threshold minus the lead time, but not yet expired) for whom an
address is on file — the founder's address for a channel. It's a chance to
keep the record by simply using it before the deadline.

Each idle spell warns at most once: a new AccountExpiryWarned /
ChannelExpiryWarned event marks the record, and the next activity stamp
clears the mark so a later idle spell warns afresh. The warning email is a
first-class api template (fedserv_api:📧:expiry_warning), alongside
the reset and confirm mails.
2026-07-14 00:18:43 +00:00
3a6239bbd9
README: list the full pseudo-client set incl OperServ 2026-07-14 00:14:01 +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
698d604954
Add a staff audit feed for notable service actions
A [log] channel now receives a one-line announcement of each notable
state change a service command makes — registrations, drops, vhosts,
suspensions, akicks, access and founder changes, bot and oper host-config
changes — naming the actor (by nick, plus account when it differs) and
what changed. The feed reads the typed event log directly: dispatch marks
the log before running a command and diffs it after, so each announcement
is exactly that command's footprint, sourced from the services server so
it reaches the channel without a pseudo-client having to be present.

Private and cosmetic events are deliberately never surfaced: memo bodies,
password/verifier material, greets, auto-join, personal and channel
cosmetic settings. Omitting [log] disables the feed.
2026-07-13 23:48:23 +00:00
34892dbba7
Harden gRPC startup and the votekick tally map
- Refuse to start the gRPC accounts API on an empty token: the
  constant-time compare treats two empty slices as equal, so a blank
  token would let an unauthenticated request through. Mirror the guard
  the JSON-RPC endpoint already had.
- Sweep expired votekick/voteban tallies on each vote. A passed vote
  removed its own key, but a tally that never reached threshold lingered
  forever, so the map could be grown without bound. The now-redundant
  per-key TTL reset goes away.
2026-07-13 23:39:49 +00:00
5d67133295
Strip CR/LF/NUL from every outbound s2s line
Free-form trailing text (message bodies, kick reasons, topics, metadata)
now passes through a single sanitizing choke-point in the serializer, so
no such text can carry an embedded line break that would smuggle a second
command onto the link. Line-based input closes the obvious path today,
but sanitizing at the point of serialization removes the whole class
regardless of where the text originated (federation, config, future
non-line sources).
2026-07-13 23:34:51 +00:00
2fd5676ff2
Clippy sweep: clean across the workspace
- Box the oversized enum variants (Event::AccountRegistered, gossip
  Msg::Entry) so small events aren't sized to a full Account; serde is
  transparent so the wire/log format is unchanged.
- Use the configured server description in the SERVER line (it was
  hardcoded and the config field went unread).
- Derive the ChanServ SET label from the ChanSetting (drops toggle's 8th
  arg); rename inspircd from_us -> sourced.
- allow the two framework-idiom lints with rationale (tonic's Status in
  authorize; the guest-rename handler's inherent arg count).
- Auto-fixed map_or/contains/split_once style lints.
2026-07-13 23:30:51 +00:00
790ec51b28
hostserv: keep the code comment neutral
Drop the name-drop from the normalize_vhost comment; the credit stays in
the previous commit message.
2026-07-13 23:06:18 +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