docs: add a full docs/ manual (architecture, configuration, modes, operators, linking, ircv3, anti-abuse, deployment, building) and refresh the README for the reactor pool + TLS-in-reactor + resilience

This commit is contained in:
Jean Chevronnet 2026-08-13 01:00:39 +00:00
parent d8a963d0f9
commit bd054f7721
11 changed files with 1182 additions and 11 deletions

View file

@ -16,9 +16,10 @@
echoIRCd is a full IRC + IRCv3 server built from the ground up in safe Rust
(`#![forbid(unsafe_code)]`) with just two dependencies — `openssl` for TLS and
`mio` for the socket engine. A single lock-free core thread owns all state while
one epoll reactor drives tens of thousands of connections without an async
runtime. It ships **100+ commands**, the **complete channel & user mode set**,
`mio` for the socket engine. A single lock-free core thread owns all state; a
**pool of epoll reactor threads** (one per core) drives the connections around it
— TLS crypto and all — without an async runtime. It ships **100+ commands**, the
**complete channel & user mode set**,
**28 IRCv3 capabilities**, server-to-server linking, a services interface, TLS,
WebSocket, GeoIP, layered anti-spam, and a JSON-RPC control plane — with every
operational limit configurable and nothing hardcoded.
@ -66,6 +67,14 @@ cargo run --release # reads ./echoircd.conf
Then point a client at it: `/server 127.0.0.1 6667` (or `6697` for TLS once a
certificate is configured).
## Documentation
The full manual lives in [`docs/`](docs/):
- [Building & running](docs/building.md) · [Configuration](docs/configuration.md) · [Architecture](docs/architecture.md)
- [Channel & user modes](docs/modes.md) · [Operators](docs/operators.md) · [Server linking & services](docs/linking.md)
- [IRCv3](docs/ircv3.md) · [Anti-abuse & flood protection](docs/anti-abuse.md) · [Deployment](docs/deployment.md)
## Configuration
Configuration is a plain `key = value` file; see
@ -80,11 +89,16 @@ A single **core thread** owns every `User` and `Channel`, so command and module
code is ordinary single-threaded logic over `&mut Server` — no `Arc<Mutex<…>>`
anywhere. The I/O edge feeds it events over channels:
- **One `mio` epoll reactor** drives all client sockets — measured at 5,000
concurrent clients on 4 threads total, scaling toward ~50k with a release build
and a high `LimitNOFILE`.
- **TLS sessions and server links** run a thread each; both hand the core the same
`OutSink`, so it never knows which transport a connection uses.
- **A pool of `mio` epoll reactors** drives client sockets — an acceptor
round-robins each connection onto a worker (one per core by default), and each
worker frames lines and runs **TLS handshakes and record crypto non-blocking**
in-thread. So the socket work and the crypto spread across cores while the state
core stays single-threaded and lock-free. (Proxied TLS and server links keep a
thread each; there are few of them.)
- **Resilience is built in.** Slow work (KDF hashing, DNS, disk snapshots) runs
off the core so a flood can't freeze it; each event and each connection's I/O is
panic-isolated so one bad client can't crash the server; a watchdog flags a
stuck core; and half-open/stalled connections are reaped on a timer.
**Why a raw reactor and not async?** IRC is one large shared mutable graph, and
almost every command mutates it and then broadcasts. With one thread owning all of
@ -92,12 +106,14 @@ it, handlers are plain synchronous code — no locks, no `.await`, no `Send + 's
bounds. A multi-threaded async runtime would force that shared state behind mutexes
or an actor mailbox, and a channel broadcast is serialized anyway, so you'd pay for
parallelism the workload can't use. `mio` is the same readiness layer async runtimes
build on, so you keep the scaling without the runtime; blocking work (DNS, TLS,
HTTP) is offloaded to its own threads.
build on, so you keep the scaling without the runtime. What *does* parallelize —
the socket syscalls and TLS crypto — runs in the reactor pool; scaling past one
machine is done by linking servers, not threading one harder.
Memory safety is structural: `Uid` handles instead of raw pointers, an `Extensible`
typemap instead of `void*` module data (freed on drop), and compiled-in trait
objects instead of a fragile plugin ABI.
objects instead of a fragile plugin ABI. **Full design notes:**
[`docs/architecture.md`](docs/architecture.md).
## Extending
@ -114,6 +130,7 @@ Three small extension points, each one file + one table line:
- **Repository**<https://git.devtronic.pro/fedserv/echoIRCd>
- **Issues**<https://git.devtronic.pro/fedserv/echoIRCd/issues>
- **Documentation** — [`docs/`](docs/)
- **Config reference** — [`echoircd.conf.example`](echoircd.conf.example)
## License

49
docs/README.md Normal file
View file

@ -0,0 +1,49 @@
# echoIRCd documentation
echoIRCd is a from-scratch IRC + IRCv3 server written in safe Rust
(`#![forbid(unsafe_code)]`) with two dependencies — `openssl` for TLS and `mio`
for the socket engine. A single lock-free core thread owns all state; a pool of
reactor threads drives the connections around it.
This folder is the reference manual. Start with whichever fits what you're doing:
| Doc | What's in it |
|-----|--------------|
| [Building & running](building.md) | Prerequisites, debug/release builds, tests, the originality guard, project layout. |
| [Configuration](configuration.md) | The config file format and a grouped reference of every setting. |
| [Architecture](architecture.md) | The single-threaded core + reactor-pool design, the I/O models, resilience, and memory safety. Read this to understand *why* it's built the way it is. |
| [Channel & user modes](modes.md) | Every prefix, list, parameter, and flag mode, plus extbans. |
| [Operators](operators.md) | The oper system, oper levels, snomasks, the override toolbox, and X-lines. |
| [Server linking & services](linking.md) | Server-to-server links, the netburst, routing, and the services / SASL interface. |
| [IRCv3](ircv3.md) | The advertised capabilities and the notable extensions. |
| [Anti-abuse & flood protection](anti-abuse.md) | The layered defenses, from the accept edge up to the application, and how they fit with kernel/upstream filtering. |
| [Deployment](deployment.md) | Running in production: release builds, a supervised service, log shipping, reverse proxies, and firewall hardening. |
## At a glance
- **Full IRC core** — registration, channels, messaging, and the informational
command set (~130 commands total).
- **Complete mode set** — the standard prefixes plus a staff prefix, list modes,
keyed/limit/flood/redirect/history/anticaps parameters, the full flag set, and
matching + acting extbans. See [modes](modes.md).
- **IRCv3** — a broad capability set including message-tags, server-time,
labeled-response, batch, echo-message, account-tag, CHATHISTORY, multiline,
message-redaction, read-marker, and relaymsg. See [IRCv3](ircv3.md).
- **Operators & services** — a rich oper toolbox with oper levels, X-lines
persisted to disk, and a services interface (SASL over links, the `SVS*` /
`ENCAP` / `METADATA` set, account-gated modes).
- **Transports** — plaintext, TLS (with client-cert fingerprints), a native
WebSocket layer, and the PROXY protocol behind a load balancer.
- **Security** — keyed host cloaking, DNSBL, GeoIP, layered connection/message
flood limits, and script/gibberish spam detection.
## Design in one paragraph
One **core thread** owns every user and channel, so command and module code is
ordinary single-threaded logic over `&mut Server` — no locks anywhere. The I/O
edge is a **pool of reactor workers** (one per core by default): they accept
connections, frame lines, and run TLS crypto, then hand the core plain events
over a channel. The core can't be frozen by slow work (KDF hashing, DNS, disk
writes all run off-thread), can't be killed by one bad connection (panics are
isolated per event and per connection), and is watched by a liveness thread. See
[architecture](architecture.md) for the full story.

116
docs/anti-abuse.md Normal file
View file

@ -0,0 +1,116 @@
# Anti-abuse & flood protection
Abuse is stopped at whichever layer can stop it most cheaply. Being honest about
which layer owns which threat matters, because the daemon **cannot** defend
against some attacks no matter how it's written.
## The three layers
```text
┌─ upstream / provider ─┐ volumetric floods (large SYN / UDP), amplification
│ (scrubbing, e.g. VAC) │ → only the network above you can absorb these
├─ host kernel / firewall┤ SYN cookies, per-IP connection-rate, conntrack caps
│ │ → cheap kernel drops before the daemon is involved
├─ echoIRCd (application)┤ connection & message floods, clones, spam content
└────────────────────────┘ → everything that survives to a real IRC session
```
**What the daemon can't do:** a **SYN flood** is spoofed TCP handshake packets
that exhaust the kernel's backlog *before* `accept()` ever returns — kernel
territory (enable `net.ipv4.tcp_syncookies`). A **UDP flood** is pure bandwidth
exhaustion, and since IRC is TCP-only the packets never reach the application at
all. Both are volumetric and are handled by the **kernel and the upstream
provider**, not by Rust. Everything below is what the application *can* do:
stop abusive *sessions and content*.
## Application layer (echoIRCd)
### At the accept edge
Rejected before any per-connection state is allocated — the cheapest point:
- **`accept_rate` / `accept_burst`** — a per-source-IP token bucket. A source
opening connections faster than the rate has its socket dropped immediately.
Off by default; trusted proxies and server links are exempt. Complements the
*concurrent* clone caps below with a *rate* cap.
- **`connflood = <max> <secs>`** and **connectban** — refuse, and optionally
z-line, an IP that opens too many connections too fast.
### Per connection
- **Clone caps** — connection classes cap concurrent connections per IP with
`localmax` (this server) and `globalmax` (network-wide).
- **`registration_timeout`** — a connection that never sends NICK+USER is dropped.
- **`tls_handshake_timeout`** — a TLS connection that opens the port but never
negotiates is reaped (it holds no session, so nothing else would).
- **`conn_waitpong`** — hold registration until the client answers a server PING
with the exact cookie. Real clients auto-reply; dumb bots never do.
- **recvq / hardsendq / softsendq** — per-connection queue caps bound memory; a
client past `softsendq` has its reads paused (backpressure), and past
`hardsendq` is dropped.
### Per message (flood control)
- **Fakelag**`flood_messages` per `flood_seconds` (opers exempt) throttles a
client sending too fast; per class, `fakelag=no` disconnects instead of
throttling. Channel modes `+f` / `+j` / `+F` add per-channel message / join /
nick-change flood limits.
### Reputation & network bans
- **Reputation** — every address accrues a score over time; the `y:<score>`
extban bans by it (`+b y:<100`).
- **DNSBL** — check connecting IPs against DNS blocklists (`mark` / `kill` /
`kline` / `gline` / `zline`).
- **X-lines** — persistent `K` / `G` / `Z` / `Q` / `CBAN` / `RLINE` bans (see
[operators](operators.md)).
### Spam content
Modules that inspect *what* users do:
| Module / setting | Catches |
|------------------|---------|
| `antirandom` | Random-looking (drone) nick/ident/realname. |
| `antimixedutf8` | Words mixing look-alike Unicode scripts. |
| `filter` / `badword` (`+G`) | Configured spam phrases / words. |
| `solvemsg` | Un-vouched users must answer an arithmetic question before PMs deliver. |
| `recaptcha` / `cloudflare_challenge` | Human-verification gate at registration. |
| `autodrop` | Pre-registration clients that blurt HTTP verbs (scanners). |
| `blockamsg` | Mass `/amsg` / `/ame` spam. |
| `restrictmsg` / `restrictcommands` / `restrictchans` / `denychans` / `channames` | Constrain who may PM, run commands, or create/name channels. |
| `dccallow` | Unwanted DCC transfers. |
| Security groups | Named user sets (`g:<name>` extban) for policy by trust level. |
## Kernel / firewall layer
The repo ships [`deploy/firewalld-echoircd.sh`](../deploy/firewalld-echoircd.sh),
a per-source-IP connection-rate limit installed through firewalld's direct-rule
interface (so firewalld owns it and won't flush it on reload):
```sh
sudo deploy/firewalld-echoircd.sh add # 30/s burst 60 per IP on the IRC ports
sudo deploy/firewalld-echoircd.sh del # remove
sudo deploy/firewalld-echoircd.sh show
```
It's **safe by design**: a policy-`ACCEPT` rule that only drops the rate-limited
*excess* to the client ports, loopback-exempt, changing no other policy. This
drops connection-churn floods in the kernel before they cost the daemon anything —
the same job as `accept_rate`, one layer lower. On a host without firewalld, the
equivalent is an `iptables`/`nftables` `hashlimit` rule, or an upstream WAF.
## A recommended baseline
1. **Kernel:** `net.ipv4.tcp_syncookies = 1` (usually already on).
2. **Firewall:** the firewalld script above (or an equivalent per-IP rate limit).
3. **Daemon:** set `accept_rate` / `accept_burst`; keep `registration_timeout`
and `tls_handshake_timeout` at sane values; enable `conn_waitpong` if bots are
a problem; add DNSBL zones; use connection classes with `localmax` / `globalmax`
to cap clones.
4. **Upstream:** rely on your provider's DDoS scrubbing for volumetric attacks —
nothing on the host can absorb a saturating flood.
Keep the firewall rate and `accept_rate` roughly aligned so the two layers agree,
and pick values generous enough that a real shared-NAT never trips them (a single
client never opens dozens of connections per second).

163
docs/architecture.md Normal file
View file

@ -0,0 +1,163 @@
# Architecture
echoIRCd is built around one idea: **all shared state lives on a single thread,
and everything that can be parallelized without touching that state is pushed off
of it.** This page explains what that means, why it was chosen, and how the pieces
fit.
## The core thread
A single **core thread** owns every `User` and `Channel` in a `Server` struct.
Command handlers, mode handlers, and module hooks are all ordinary synchronous
functions that take `&mut Server` and mutate it directly. There is no
`Arc<Mutex<…>>`, no `RwLock`, no actor mailbox, and no `.await` anywhere in the
command path.
The core runs one loop:
```text
for ev in rx { // rx is an mpsc channel fed by the I/O edge
handle_event(ev) // Connect / Line / Disconnect / Tick / async results
}
```
Every state change funnels through this loop, so there is exactly one writer to
the state graph and no data races are possible by construction.
### Why not an async runtime?
IRC is one large shared mutable graph. Almost every command reads part of it,
mutates part of it, and then broadcasts to many connections — a channel message
touches the send buffers of everyone in the channel, a nick change updates a
global map and notifies every common-channel peer, and so on. This workload has
two properties that decide the design:
1. **The write set is global and interconnected.** You cannot cleanly shard the
state by connection, because messages cross shards constantly.
2. **The per-message CPU cost is tiny.** Parse a line, look up a channel, append
bytes to some send buffers. It is almost entirely I/O-bound.
Put those together and a multi-threaded async runtime buys you nothing here:
you'd have to wrap the shared graph in a global lock (serializing everything you
just spread across threads) or an actor that processes messages one at a time
(a single-threaded loop with extra steps). Meanwhile you'd pay for `Send +
'static` bounds on every future and a scheduler you don't need. So the core stays
single-threaded and lock-free, and `mio` provides the same readiness layer an
async runtime would build on — without the runtime.
The thing that single-threaded state *can't* do is use more than one core. That's
fine, because the two things that actually benefit from multiple cores — the
socket syscalls and the TLS crypto — don't touch shared state at all. They run in
the reactor pool.
## The reactor pool
Client connections are served by a **pool of reactor threads**, sized by
`io_threads` (default: one per CPU, capped). The shape is:
```text
┌──────────── acceptor ────────────┐
listener ───▶ │ accept(), pick a worker (round- │
│ robin), hand off the socket │
└───────┬──────────┬──────────┬─────┘
▼ ▼ ▼
reactor 0 reactor 1 reactor N (each: its own mio poll,
its own conns map, its own token space)
│ │ │
└──────────┴──────────┘
Event channel ──▶ core thread (single, lock-free)
```
- One **acceptor** owns the listener, accepts connections, and round-robins each
new socket onto a worker.
- Each **worker** runs its own `mio` poll loop over its own shard of connections.
It reads bytes, frames complete lines, and sends the core `Line` / `Connect` /
`Disconnect` events. Tokens are worker-local; the shared atomic counter only
mints globally-unique `Uid`s.
- The **core** processes those events serially. Output flows back the other way:
when the core writes to a connection, the write is routed to the owning worker,
which drains it to the socket.
Because workers only frame bytes and feed the core, and the core owns all state,
the parallel part needs **no shared locking** — the only cross-thread contact is
the lock-free event channel.
## The I/O models
Three transports coexist behind one `OutSink` handle, so the core never knows or
cares which one a connection uses:
| Transport | Model | Notes |
|-----------|-------|-------|
| **Plaintext clients** | reactor pool | The common case; one worker frames many sockets. |
| **Direct TLS clients** | reactor pool | The handshake and record crypto run **non-blocking inside the worker** (OpenSSL driven off a `mio` socket). TLS work spreads across cores like everything else. |
| **Proxied TLS** (a PROXY header before the handshake) | thread per connection | Reading the pre-handshake header wants the simpler blocking path; there are few of these. |
| **Server links** | thread per connection | A handful of long-lived peers; not worth multiplexing. |
Scaling out to more machines is done by **linking servers** (see
[linking](linking.md)), not by threading one server harder — the single core is
the correct unit, and the network grows by adding nodes.
## Resilience
A single-threaded core has an obvious risk: one slow or crashing thing could
freeze or kill everyone. Each of those failure modes is closed off:
- **Nothing slow runs inline.** Deliberately-slow work is offloaded to bounded
worker threads and delivered back as an event:
- **KDF password hashing** (bcrypt / pbkdf2) for `OPER`, `PASS` connect-class
checks, `TITLE`, and `MKPASSWD` — a flood of auth attempts can't freeze the
core.
- **DNS, ident, and HTTP** lookups.
- **Disk snapshot writes** (reputation, channel metadata, X-lines) go through a
coalescing background writer, so a slow or full disk never stalls the event
loop. Writes are **atomic** (temp file + rename), so a crash mid-write can't
leave a truncated file.
- **One panic can't take down the server.** Each event is handled inside
`catch_unwind`, and in the reactor each connection's reads/writes are isolated —
a panic parsing one client's bytes drops *that* client and logs it, never the
worker that serves everyone else.
- **A stuck core is visible.** A watchdog thread reads a shared "busy since"
marker and logs if the core stays on one event past `watchdog_ms`; slow events
also raise a server notice (`slow_command_ms`). In production a liveness probe
restarts the service if a register round-trip stops answering.
- **Half-open connections are reaped.** A connection that never registers is
dropped after `registration_timeout`; a TLS connection that opens the port but
never negotiates is dropped after `tls_handshake_timeout`.
See [anti-abuse](anti-abuse.md) for how these combine with flood limits and
kernel-level filtering.
## Memory safety
Safety is structural, not just `#![forbid(unsafe_code)]`:
- **Handles, not pointers.** Users and channels are referenced by `Uid` /
channel-key handles looked up in maps, so there are no dangling references and
no use-after-free.
- **A typemap, not `void*`.** Modules attach per-user / per-channel / per-server
state through an `Extensible` typemap keyed by Rust type; it's dropped
automatically with its owner, so module state can't leak or be freed twice.
- **Trait objects, not a plugin ABI.** Commands, modes, and modules are
compiled-in trait objects. There is no dynamic-loading FFI boundary to get
wrong.
The two dependencies (`openssl`, `mio`) keep their own `unsafe` internal, so the
daemon itself never writes any. The `scripts/native-rust-guard.sh` check enforces
all of this on every edit: no `unsafe`, no C/FFI, dependencies limited to those
two, and no code copied or translated from another project.
## Tuning knobs
| Setting | Effect |
|---------|--------|
| `io_threads` | Reactor workers; `0` = auto (one per core, capped). Raise for very high connection/packet rates. |
| `max_line` / `max_sendq` | Per-connection receive/send-queue caps (per-class overrides exist). |
| `slow_command_ms` / `watchdog_ms` | Core-health visibility. |
| `tls_handshake_timeout` | Reap stalled TLS handshakes. |
| `LimitNOFILE` (OS) | File-descriptor ceiling; must be high to reach tens of thousands of connections. |
For a serious deployment, always run a **release build** — a debug build is
unoptimized and dramatically slower on the CPU-bound paths (TLS, hashing,
cloaking, parsing). See [deployment](deployment.md).

100
docs/building.md Normal file
View file

@ -0,0 +1,100 @@
# Building & running
## Prerequisites
- A stable **Rust** toolchain (`cargo`, `rustc`).
- **OpenSSL** development headers (the `openssl` crate links against the system
library) — e.g. `libssl-dev` on Debian/Ubuntu.
That's it. There are exactly two dependencies: `openssl` and `mio`.
## Build
```sh
git clone https://git.devtronic.pro/fedserv/echoIRCd
cd echoIRCd
# development build (fast to compile, slow to run)
cargo build
# release build — ALWAYS use this in production; the debug build is unoptimized
# and far slower on the CPU-bound paths (TLS, hashing, cloaking, line parsing)
cargo build --release
```
The binaries land in `target/debug/echoircd` and `target/release/echoircd`.
## Run
echoircd takes one argument, the path to a config file (default `./echoircd.conf`):
```sh
cp echoircd.conf.example echoircd.conf # then edit: oper pass, cloak_key, TLS paths
cargo run --release # reads ./echoircd.conf
# or run the binary directly:
./target/release/echoircd /path/to/echoircd.conf
```
Point a client at it: `/server 127.0.0.1 6667`, or `6697` for TLS once a
certificate is configured. See [configuration](configuration.md) for every
setting and [deployment](deployment.md) for running it as a supervised service.
> Your live `echoircd.conf` is gitignored on purpose — it holds secrets (oper
> password, cloak key, link password). Never commit it.
## Tests
```sh
cargo test # unit + integration tests
cargo test --test integration # just the end-to-end suite
```
The integration suite spawns the real binary on ephemeral ports and drives it as
a client — covering reactor-pool cross-worker delivery, TLS-in-reactor handshakes,
the stalled-handshake reap, the accept-rate limiter, and nick collisions. It
tracks and kills its child processes by PID, never by name.
## The originality guard
Every source edit is checked by `scripts/native-rust-guard.sh`, which enforces the
project's invariants:
- no `unsafe` (the crate is `#![forbid(unsafe_code)]`),
- no C / FFI,
- dependencies limited to `openssl` + `mio`,
- and no code copied or translated from any other project — everything is
original Rust.
Run it on a file directly with `bash scripts/native-rust-guard.sh <file>`.
## Project layout
```text
src/
main.rs startup: config, listeners, the reactor pool, the core thread
ircd.rs the core event loop and event types
server.rs the Server: all state, plus helpers (send, snotice, off-core work)
socketengine.rs the I/O edge: reactor pool, acceptor, TLS-in-reactor, links
tls.rs the TLS backend (OpenSSL) — blocking + non-blocking sessions
channels.rs Channel, membership, and the channel mode struct
users.rs User and session state
mode.rs ChanMode / UserMode traits and the mode registry
command.rs the Command trait
module.rs the module lifecycle-hook trait
coremods/ built-in commands (registration, channels, messaging, oper, …)
modules/ optional, pluggable modules (~70 of them)
tests/ end-to-end integration tests
deploy/ production systemd units + firewall script
docs/ this manual
```
## Extending
Three extension points, each one file plus one table entry:
- **Commands** (`src/command.rs`, `src/coremods/`) — a handler with `name`,
`min_params`, `before_reg`, and `handle(&mut Server, uid, params)`.
- **Modes** (`src/mode.rs`) — channel/user modes as `ChanMode` / `UserMode`
handler objects; adding one never touches the parser.
- **Modules** (`src/module.rs`, `src/modules/`) — lifecycle hooks; pre-hooks can
**Deny** a register/command/message, notify-hooks fire after.

172
docs/configuration.md Normal file
View file

@ -0,0 +1,172 @@
# Configuration
The config is a plain `key = value` text file (default `./echoircd.conf`). Some
keys repeat to build a list (`motd`, `oper`, `link`, `connectclass`, `dnsbl`,
`securitygroup`, …). Comments start with `#`.
The shipped [`echoircd.conf.example`](../echoircd.conf.example) is the fully
annotated master reference — every key with its default. This page organizes those
keys by topic. **Every operational limit is a config key**; nothing is hardcoded.
Most settings apply on `REHASH` without a restart.
> `echoircd.conf` is gitignored because it holds secrets (oper password, cloak
> key, link password). Never commit your live config.
## Server identity
| Key | Meaning |
|-----|---------|
| `servername` | This server's name (e.g. `irc.example.net`). |
| `network` | Network name (advertised in ISUPPORT). |
| `sid` | Server ID for linking — 3 chars, first a digit (e.g. `0AA`). |
| `serverdesc` | Human-readable server description. |
| `motd` | Message of the day; repeat the key for extra lines. |
## Listeners & TLS
| Key | Meaning |
|-----|---------|
| `bind` | Plaintext client listener, `ip:port` (e.g. `0.0.0.0:6667`). |
| `bind_tls` | TLS client listener (e.g. `0.0.0.0:6697`). |
| `tls_cert` / `tls_key` | PEM certificate + private key for TLS (and `wss://`). |
| `bind_server` | Server-to-server link listener (see [linking](linking.md)). |
Generate a self-signed cert to start:
```sh
openssl req -x509 -newkey rsa:2048 -keyout tls/key.pem -out tls/cert.pem \
-days 3650 -nodes -subj "/CN=irc.example.net"
```
## I/O & performance
| Key | Default | Meaning |
|-----|---------|---------|
| `io_threads` | `0` (auto) | Reactor workers; `0` = one per core, capped. |
| `max_line` | `16384` | Max bytes in one line / receive queue. |
| `max_sendq` | `1048576` | Max queued output before a slow client is dropped. |
| `tls_handshake_timeout` | `15` | Drop a TLS conn that stalls mid-handshake (secs; `0` = off). |
| `slow_command_ms` | `200` | Server-notice when one event takes at least this long (`0` = off). |
| `watchdog_ms` | `5000` | Log if the core is stuck on one event this long (`0` = off). |
## Connection classes
`connectclass = <name> key=value …` — per-class connection policy. Each line
matches connecting clients by IP/host mask (glob **or** CIDR) and optional
TLS/port; the first match wins, else the global limits apply. Masks are tested at
connect (against the IP) and re-tested at registration (against the resolved
host).
Key options: `allow=<mask[,mask]>`, `deny=yes`, `parent=<name>` (inherit),
`requiressl=yes|trusted`, `password=<pw>` + `hash=<algo>`, `port=<p[,p]>`,
`localmax=<n>` (per-IP, this server), `globalmax=<n>` (per-IP, network-wide),
`limit=<n>` (total users in class), `maxchans=<n>`, `pingfreq=<secs>`,
`timeout=<secs>` (registration), `modes=<+modes>`, `recvq` / `hardsendq` /
`softsendq` (queue caps), `fakelag=no` (disconnect flooders instead of throttling),
`penaltythreshold` / `commandrate` (flood window), `useident=yes`,
`requireident=yes`, `resolvehostnames=no`, `maxconnwarn=yes`.
```text
connectclass = trusted allow=10.0.0.0/8 maxchans=200 pingfreq=120 fakelag=no
connectclass = secure allow=* requiressl=yes password=sha256:<hex> hash=sha256
connectclass = vpn allow=* parent=trusted localmax=2 maxchans=20 modes=+ix
connectclass = banned allow=1.2.3.0/24 deny=yes
connectclass_required = yes # refuse clients matching no allow class (default no)
```
## Connection policy & timeouts
| Key | Default | Meaning |
|-----|---------|---------|
| `registration_timeout` | `60` | Drop clients that never send NICK+USER in time. |
| `ping_frequency` | `90` | Send a PING after this much idle. |
| `ping_timeout` | `60` | Then drop if no PONG within this much longer. |
| `resolve_hosts` | `on` | Do reverse DNS on connect. |
| `use_resolved_host` | `on` | Use the resolved host in the mask (else keep the IP). |
| `useident` / `requireident` / `ident_timeout` | off / off / `5` | RFC 1413 ident lookups. |
| `conn_waitpong` | off | Hold registration until the client PONGs a cookie (bot filter). |
| `abbreviation` | off | Let a unique command prefix resolve (`WHOI``WHOIS`). |
## Operators
| Key | Meaning |
|-----|---------|
| `oper = <name> <password> [level]` | An oper account; the password may be hashed (see `MKPASSWD`). Optional numeric [oper level](operators.md). |
| `opermotd` | A line shown to opers via `/OPERMOTD` (repeatable). |
| `operprefix` | Give every oper a `!` prefix in their channels. |
| `ojoin` / `ojoin_op` | Enable `/OJOIN` (join as staff, with op unless `ojoin_op = no`). |
## Linking & services
| Key | Meaning |
|-----|---------|
| `link = <name> <ip> <port> <password> [autoconnect]` | A peer server; password is a shared secret. |
| `sasl_server` | The linked server that handles SASL (relayed AUTHENTICATE). Unset disables SASL. |
| `webirc = <password> [gateway] [ip-mask]` | Trust a web gateway's `WEBIRC` (real client host/IP). |
See [linking](linking.md) for the full picture.
## Anti-abuse
| Key | Meaning |
|-----|---------|
| `accept_rate` / `accept_burst` | Per-IP new-connection rate limit at the accept edge (`0` = off). |
| `flood_messages` / `flood_seconds` | Per-user message-rate limit (opers exempt). |
| `connflood = <max> <secs>` | Refuse an IP opening more than `max` connections per `secs`. |
| `dnsbl` / `dnsbl_action` / `dnsbl_reason` / `dnsbl_duration` | DNS blocklist checks (`mark` / `kill` / `kline` / `gline` / `zline`). |
| `antimixedutf8` + `amu_*` | Block spam mixing look-alike scripts. |
| `badword = <find> [replacement]` | `+G` censor list. |
| `autodrop_commands` | Silently drop pre-registration clients that send these (HTTP scanners). |
| `solvemsg` | Make un-vouched users answer an arithmetic question before their PMs deliver. |
| `dccallow_*` | Filter unwanted DCC transfers. |
See [anti-abuse](anti-abuse.md) for how these layer together.
## Identity & privacy
| Key | Meaning |
|-----|---------|
| `cloak_key` | Secret for host cloaking (`+x`). A long random hex string; changing it re-cloaks everyone. |
| `vhost = <user> <pass> <host>` | A self-service vhost claimable with `/VHOST`. |
| `customprefix` | Reconfigure prefix tiers or define brand-new prefixes. |
| `hidewhois` + `hidewhois_*` | Hide sensitive WHOIS lines from ordinary users. |
| `hidemode = <mode> <rank>` / `hidelist = <mode> <rank>` | Restrict who sees mode changes / list-mode entries. |
## GeoIP, reputation & security groups
| Key | Meaning |
|-----|---------|
| `geoip_database` | Path to a MaxMind `GeoLite2-Country.mmdb`; enables the `G:<cc>` extban, `GEOIP` command, WHOIS country. |
| `reputation_*` / `reputationexpire` | Per-address reputation scoring and the `y:<score>` extban. |
| `securitygroup = <name> [criteria…]` | A named user set usable as the `g:<name>` extban. |
## Logging
| Key | Meaning |
|-----|---------|
| `syslog` + `syslog_target` / `syslog_facility` / `syslog_tag` | Mirror the notice/log stream to the system logger (`/dev/log` or `host:port`). |
| `log_json` | Append the notice/log stream to a file as JSON lines. |
| `chanlog` | Mirror the oper server-notice stream into a channel. |
## Transports
| Key | Meaning |
|-----|---------|
| `proxy = <glob\|CIDR>` | Trust the PROXY protocol header from these sources (real client IP). Repeatable. |
| `bind_ws` / `bind_wss` | WebSocket listeners (`ws://` / `wss://`). |
| `ws_origin`, `ws_proxyranges`, `ws_trust_proxy`, `ws_timeout`, … | WebSocket policy — see the example config. |
## Advertised limits
Each is a config key with a built-in default; set a line only to override.
| Key | Default | | Key | Default |
|-----|---------|-|-----|---------|
| `maxnick` | `30` | | `maxwatch` | `128` |
| `maxchannel` | `50` | | `maxmonitor` | `128` |
| `whowas_maxentries` | `256` | | `maxsilence` | `32` |
| `chathistory_limit` | `256` | | `maxaccept` | `64` |
| `multiline_maxbytes` | `4096` | | `multiline_maxlines` | `24` |
For the complete, per-key annotated list including every module's options, see
[`echoircd.conf.example`](../echoircd.conf.example).

125
docs/deployment.md Normal file
View file

@ -0,0 +1,125 @@
# Deployment
Running echoIRCd in production. The repo ships ready-to-use units and scripts in
[`deploy/`](../deploy).
## 1. Build a release binary
Always run the **release** build in production — a debug build is unoptimized and
far slower on the CPU-bound paths (TLS crypto, password hashing, cloaking, line
parsing):
```sh
cargo build --release
```
## 2. Pin the binary
Copy the release binary to a **stable path** and run *that*, not `target/`. This
decouples "what's running" from "what you're compiling" — a stray `cargo build`
can never change what a restart would launch:
```sh
mkdir -p bin
cp target/release/echoircd bin/echoircd
```
`bin/` is gitignored. **Deploying an update** is then: build release → copy over
`bin/echoircd` → restart the service.
## 3. Supervise it with systemd
[`deploy/echoircd-dev.service`](../deploy/echoircd-dev.service) runs the pinned
binary, restarts on failure, raises the file-descriptor limit, and starts on boot:
```ini
[Service]
Type=simple
User=youruser
WorkingDirectory=/path/to/echoIRCd
LimitNOFILE=200000
ExecStart=/path/to/echoIRCd/bin/echoircd /path/to/echoIRCd/echoircd.conf
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
```
Install it (adjust the paths / `User` first):
```sh
sudo cp deploy/echoircd-dev.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now echoircd-dev.service
```
`LimitNOFILE` is what lets the reactor pool reach tens of thousands of
connections — each connection is a file descriptor. `Restart=on-failure` means a
crash self-heals instead of leaving the network down.
## 4. Add a liveness probe
`Restart=on-failure` catches a *crash*, but not a *hang* (a process that's alive
but stopped answering). [`deploy/echoircd-liveness.timer`](../deploy/echoircd-liveness.timer)
runs [`scripts/liveness.sh`](../scripts/liveness.sh) every couple of minutes: it
does a real NICK/USER register round-trip on the plaintext port and restarts the
service if it doesn't get a welcome.
```sh
sudo cp deploy/echoircd-liveness.service deploy/echoircd-liveness.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now echoircd-liveness.timer
```
## 5. Harden the firewall
Add the kernel-layer connection-rate limit (see [anti-abuse](anti-abuse.md)):
```sh
sudo deploy/firewalld-echoircd.sh add
```
For volumetric (SYN/UDP) floods, rely on your upstream provider's scrubbing —
nothing on the host can absorb a saturating flood.
## Behind a reverse proxy / load balancer
If a TCP proxy (HAProxy, nginx stream) sits in front, enable the **PROXY protocol**
so the real client IP is used instead of the proxy's:
```text
proxy = 10.0.0.0/8 # trust the PROXY header from these sources (repeatable)
```
A connection from a trusted proxy must lead with a PROXY (v1 or v2) header. This
applies to the plaintext and TLS client listeners; for browser clients over
WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead.
## Log shipping
| Setting | Sends the notice/log stream to |
|---------|--------------------------------|
| `syslog = yes` (+ `syslog_target`) | the system logger (`/dev/log` or `host:port`) |
| `log_json = <path>` | a file, as JSON lines (easy to ingest) |
| `chanlog = #snotices` | an in-network channel staff can watch |
The core also surfaces its own health: `slow_command_ms` raises a notice when an
event runs long, and `watchdog_ms` logs if the core is stuck.
## TLS certificates
Point `tls_cert` / `tls_key` at your PEM files (the same pair serves `bind_tls`
and `wss://`). After renewing a certificate, `REHASH` reloads it without dropping
the server. Client-certificate fingerprints are read automatically for SASL
EXTERNAL / CertFP.
## Updating checklist
1. `git pull` && `cargo build --release`
2. `cargo test` (the integration suite spawns the binary and exercises the real
paths)
3. `cp target/release/echoircd bin/echoircd`
4. `sudo systemctl restart echoircd-dev.service`
5. Confirm: `systemctl is-active echoircd-dev.service`, then check a client
connects on the TLS port.

71
docs/ircv3.md Normal file
View file

@ -0,0 +1,71 @@
# IRCv3
echoIRCd implements a broad set of [IRCv3](https://ircv3.net) capabilities. A
client negotiates them with `CAP LS` / `CAP REQ`; `cap-notify` keeps the client
informed as they change. This page groups what's supported.
## Message tags & framing
| Capability | What it adds |
|------------|--------------|
| `message-tags` (+ `msgid`) | Arbitrary message tags, and a unique `msgid` on messages. |
| `server-time` | A `time` tag with the server's timestamp on each message. |
| `account-tag` | The sender's logged-in account as a tag. |
| `echo-message` | The server echoes your own `PRIVMSG`/`NOTICE` back to you. |
| `labeled-response` | Correlate a response batch to the command that caused it. |
| `batch` | Group related messages (history playback, netjoins, …). |
| `standard-replies` | Structured `FAIL` / `WARN` / `NOTE` replies. |
| `TAGMSG` | A tag-only message (e.g. typing / reactions) with no text body. |
## Membership & identity
| Capability | What it adds |
|------------|--------------|
| `extended-join` | Account and real name included in `JOIN`. |
| `account-notify` | Notified when a user logs in / out of an account. |
| `away-notify` | Notified when a user's away state changes. |
| `chghost` | Notified when a user's host/ident changes (instead of a rejoin). |
| `setname` | Change your real name in-session (`SETNAME`). |
| `multi-prefix` | See all of a member's status prefixes at once. |
| `userhost-in-names` | Full `nick!user@host` in `NAMES`. |
| `invite-notify` | Channel ops see invites to their channel. |
## Authentication
| Capability | What it adds |
|------------|--------------|
| `sasl` | `AUTHENTICATE` with **PLAIN** or **EXTERNAL** (client-cert / CertFP), relayed to the services server. See [linking](linking.md). |
| `draft/account-registration` | Create and confirm an account in-band with `REGISTER` / `VERIFY`. |
## History & messaging
| Capability | What it adds |
|------------|--------------|
| `draft/chathistory` | `CHATHISTORY` — fetch recent messages for a conversation. |
| `draft/message-redaction` | `REDACT` — delete/redact a prior message. |
| `draft/multiline` | Send one logical message spanning multiple lines. |
| `draft/read-marker` | `MARKREAD` — set/query the last-read point of a conversation. |
| `draft/relaymsg` | `RELAYMSG` — speak under a spoofed relay nick (for bridges). |
## Monitoring & notifications
`MONITOR` (+ `extended-monitor`), the legacy `WATCH` list, `SILENCE`, and
caller-id (`ACCEPT` + user mode `+g`) let clients track other users' presence and
control who may message them.
## Metadata & misc
| Capability / feature | What it adds |
|----------------------|--------------|
| `draft/metadata-2` | `METADATA` key/value data on users and channels. |
| `draft/extended-isupport` | Re-request the current ISUPPORT tokens on demand. |
| `draft/json-log` | Stream the server log to an oper as JSON. |
| `EXTJWT` | A short-lived, server-signed HS256 token a client can present elsewhere. |
| network icon / profile link | Advertise a network icon and per-account profile URLs. |
## ISUPPORT
On registration the server advertises its limits and features via `RPL_ISUPPORT`
(005), including `PREFIX`, `CHANMODES`, `EXTBAN` (see [modes](modes.md)), `WHOX`,
`CHATHISTORY`, `MONITOR` / `WATCH` / `SILENCE` sizes, `NICKLEN` / `CHANNELLEN`,
`CASEMAPPING=ascii`, `UTF8ONLY`, and `NETWORK`.

111
docs/linking.md Normal file
View file

@ -0,0 +1,111 @@
# Server linking & services
Multiple echoIRCd servers form one network by **linking**. Users and channels are
shared across all servers, and a services package (accounts, nick/channel
registration) attaches as just another linked server.
## Setting up a link
Each server needs a unique **SID** (3 characters, first a digit) and a link
listener:
```text
sid = 0AA
bind_server = 0.0.0.0:7000
```
Then declare each peer, with a shared-secret password on both sides:
```text
# link = <name> <ip> <port> <password> [autoconnect]
link = peer.example.net 203.0.113.5 7000 SHARED_LINK_SECRET autoconnect
```
`autoconnect` dials the peer on startup; otherwise an oper runs
`/CONNECT peer.example.net`. Link connections use the thread-per-connection I/O
model (there are only a handful of them) — see [architecture](architecture.md).
## The netburst
When two servers link, they exchange a **burst** that synchronizes state:
- **Servers** — every server the peer knows, so the topology is complete.
- **Users** — each as a `UID` introduction carrying nick, ident, host, real name,
modes, account, and cloak.
- **Channels** — each as an `FJOIN` carrying the member list with their status
prefixes, plus the channel's modes, topic, and ban lists.
After the burst both sides have identical state and stay in sync by propagating
every subsequent change.
## Routing, collisions & netsplits
- **Multi-hop routing** — a message for a remote user is forwarded hop-by-hop
toward the server that owns them; the ircd tracks which direction each SID lies.
- **Nick collisions** — if the same nick appears on both sides of a new link, the
collision is resolved deterministically (by sign-on time / UID) so the network
converges to one owner.
- **Netsplits** — when a link drops, every user and channel behind it is cleanly
removed locally and re-introduced on reconnect via a fresh burst.
## Services & accounts
echoIRCd stays a **pure ircd**: it does not implement NickServ/ChanServ itself.
Instead it carries the *interface* a services package uses, and services run as a
linked server. That interface is:
### SASL
Client authentication is relayed to the configured services server:
```text
sasl_server = services.example.net
```
- **PLAIN** — the client's credentials are forwarded to services over the link.
- **EXTERNAL** — the client must be on TLS with a client certificate; its
fingerprint (CertFP) is forwarded, so services can match it to an account with
no password.
With `sasl_server` unset, SASL is disabled.
### Account state & gated modes
A logged-in user carries an **account** name (advertised via `account-tag` /
`extended-join` / WHOIS, and user mode `+r`). Modes and channel policies can be
gated on being logged in — e.g. channel `+R` (registered users only may join),
`+M` (only registered may speak), and the `g:<group>` / account-based extbans.
### The services command set
Services drive the network through the `SVS*` family and generic transports:
- `SVSNICK`, `SVSJOIN`, `SVSPART`, `SVSMODE` — act on a user's nick/membership/modes.
- `SVSLOGIN` / `SVSLOGOUT` — set or clear a user's account.
- `SVSHOLD`, `SVSTOPIC`, `SVSOPER`, `SVSCMODE` — hold a nick, set a topic, grant
oper, set channel modes with service authority.
- `ENCAP` — an encapsulated command routed to a specific server.
- `METADATA` — attach arbitrary key/value data to users and channels.
### Optional ircd-side registration
Independently of a services package, the server can offer IRCv3
`draft/account-registration` directly: a client uses `REGISTER` / `VERIFY` to
create and confirm an account.
## Web gateways (WEBIRC)
A trusted web-based client gateway (the browser-to-IRC kind) can declare the real
client's host and IP so users don't all appear to come from the gateway:
```text
# webirc = <password> [gateway-name] [ip-mask]
webirc = WEBIRC_SECRET mygateway 203.0.113.9
```
The `ip-mask` restricts which source IP may use the password — always set it. The
gateway sends a `WEBIRC` line at connect with the password and the real client
details.
> For browser clients connecting *directly* (no gateway), use the native
> [WebSocket transport](configuration.md#transports) instead.

142
docs/modes.md Normal file
View file

@ -0,0 +1,142 @@
# Channel & user modes
This is the full mode set echoIRCd advertises. The authoritative ISUPPORT string
is:
```text
PREFIX=(qaohv)~&@%+ CHANMODES=beIgXw,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnpstuz
EXTBAN=,Gbcgjmnrsy
```
`CHANMODES` groups modes into four types: **A** = list modes (always take a mask),
**B** = always take a parameter, **C** = take a parameter only when set, **D** =
flags with no parameter.
## Channel status (prefix) modes
Granted to members; each has a name prefix shown before the nick.
| Mode | Prefix | Rank | Meaning |
|------|--------|------|---------|
| `+q` | `~` | owner | Channel owner — full control. |
| `+a` | `&` | admin | Protected/admin — like op, can't be kicked/deopped by ops. |
| `+o` | `@` | op | Channel operator. |
| `+h` | `%` | halfop | Half-operator — a reduced op. |
| `+v` | `+` | voice | May speak when the channel is `+m`. |
Network staff (IRC operators) can additionally carry a `!` prefix (mode `y`) via
[operprefix](operators.md), which ranks above owner.
## List modes (type A)
Take a mask and maintain a list; `MODE #chan +b` with no argument lists entries.
| Mode | Name | Meaning |
|------|------|---------|
| `+b` | ban | Ban a `nick!user@host` mask (accepts [extbans](#extbans)). |
| `+e` | ban exempt | A mask exempt from `+b`. |
| `+I` | invite exempt | A mask that may join a `+i` channel without an invite. |
| `+g` | filter | Block matching users from speaking (a channel-scoped mute list). |
| `+X` | exempt-chanops | Masks whose members bypass selected restrictions. |
| `+w` | auto-status | `+w <prefix>:<hostmask>` grants a status prefix on join, e.g. `+w o:*!*@trusted.host` (auto-op), `+w v:*!*@*.friend.net` (auto-voice). |
## Parameter modes
**Type B — always a parameter:**
| Mode | Meaning |
|------|---------|
| `+k <key>` | Channel key; must be supplied to join. |
**Type C — a parameter only when setting:**
| Mode | Meaning |
|------|---------|
| `+l <n>` | Member limit. |
| `+f [*]<lines>:<secs>` | Message flood: kick past `lines` messages in `secs`; a leading `*` also bans. |
| `+j <count>:<secs>` | Join flood throttle. |
| `+F <count>:<secs>` | Nick-change flood throttle. |
| `+L <#target>` | Redirect joiners here when the channel is full/keyed/invite-only. |
| `+H <lines>:<secs>` | Replay recent messages to joiners (in-channel history). |
| `+B <percent>` | Anti-caps: block messages that are more than `percent` uppercase. |
| `+J <secs>` | Block rejoin for `secs` after a kick. |
| `+d <secs>` | New joiners can't speak for `secs`. |
| `+K <n>` | Block a line repeated within a member's last `n` messages. |
## Flag modes (type D)
No parameter.
| Mode | Meaning | | Mode | Meaning |
|------|---------|-|------|---------|
| `+i` | invite-only | | `+S` | strip formatting/colour |
| `+m` | moderated (only `+ov` speak) | | `+R` | registered users only may join |
| `+n` | no external messages | | `+M` | only registered users may speak |
| `+p` | private (hidden from WHOIS) | | `+G` | censor configured bad words |
| `+s` | secret | | `+u` | auditorium (hide non-ops) |
| `+t` | only ops set the topic | | `+Q` | KICK disabled |
| `+z` | TLS-only join | | `+A` | any member may INVITE |
| `+O` | opers only may join | | `+P` | permanent (survives 0 members) |
| `+N` | no nick changes while joined | | `+U` | op-moderated (unprivileged msgs go to ops) |
| `+C` | block CTCP | | `+D` | delay-join (hide JOIN until they speak) |
| `+T` | block NOTICEs | | `+c` | reject formatting/colour |
Channel modes can also be set or queried by long name with the `PROP` command
(e.g. `PROP #chan moderated=on`).
## User modes
| Mode | Meaning |
|------|---------|
| `+i` | invisible (hidden from WHO / global `WHOIS` channel list) |
| `+w` | receive `WALLOPS` |
| `+o` | IRC operator (set only via `OPER`) |
| `+x` | cloaked host (keyed masking; usually auto-set on connect) |
| `+r` | logged into an account (server-set; can't be self-applied) |
| `+z` | only accept private messages from TLS-connected users |
| `+B` | flagged as a bot |
| `+D` | deaf — ignore channel messages |
| `+I` | hide your channel list in WHOIS |
| `+H` | hide your oper status |
| `+R` | block private messages from users not logged into an account |
| `+g` | caller-id — only people you `ACCEPT` may message you |
| `+W` | be notified when someone WHOISes you |
| `+c` | block private messages from users with no common channel |
| `+h` | available for help (helpop; oper-settable) |
| `+s` | server-notice mask — see [snomasks](operators.md#snomasks) |
## Extbans
Extended bans extend any ban-style list (`+b`, `+e`, `+I`, `+g`, …) beyond plain
host masks. `EXTBAN=,Gbcgjmnrsy` — there is no prefix character; a ban simply
starts with the extban letter and a colon.
**Matching extbans** — match a user by something other than host, usable with any
list mode:
| Extban | Matches |
|--------|---------|
| `g:<name>` | members of a named [security group](configuration.md). |
| `y:<score>` | [reputation](configuration.md) score, e.g. `y:<100` (below 100), `y:>500`. |
| `r:<mask>` | real name (GECOS). |
| `j:<#chan>` | users who are also in `#chan`. |
| `s:<mask>` | the server a user is on. |
| `G:<cc>` | GeoIP country code, e.g. `G:CN,RU`. |
| `b:<#chan>` | anyone banned in `#chan` (shares a ban list between channels). |
**Acting extbans** — change *what* matched users can do rather than blocking them
outright (used with `+b`):
| Extban | Effect on matched users |
|--------|-------------------------|
| `m:<mask>` | muted — can't speak (but stay joined). |
| `c:<mask>` | can't send formatting/colour. |
| `n:<mask>` | can't change nick. |
Example — quarantine everyone from a bad network to read-only, and bounce a
spammer to another channel:
```text
/MODE #main +b m:*!*@*.spammer.net
/MODE #main +b *!*@*.badhost$#quarantine
```

105
docs/operators.md Normal file
View file

@ -0,0 +1,105 @@
# Operators
IRC operators ("opers") are staff with elevated privileges. This page covers
becoming an oper, the command toolbox, snomasks, and the ban ("X-line") system.
## Becoming an operator
Define oper accounts in the config:
```text
oper = admin CHANGE_THIS_PASSWORD # plaintext (fine behind a private config)
oper = helper sha256:<hex> # or a hashed password
oper = root $2b$12$… # bcrypt is supported too
```
A client authenticates with `/OPER <name> <password>`, gains user mode `+o`, and
(optionally) a staff prefix — see [operprefix](configuration.md). Generate a
hashed password with the oper-only `/MKPASSWD <algo> <password>` command
(`md5`, `sha1`, `sha256`, `sha512`, `pbkdf2`, `bcrypt`). KDF hashes are verified
off the core thread, so an `OPER` flood can't freeze the server.
### Oper levels
An `oper` block may carry a trailing numeric **level** (`oper = <name> <pass>
<level>`). Levels gate sensitive actions — for example, a higher-level oper can't
be `KILL`ed by a lower-level one. Levels are advisory policy layered on top of the
`+o` flag.
## Snomasks
Server-notice masks (`+s`) subscribe an oper to categories of the server's live
event stream — connects, floods, link events, and so on. Set them as a
mode parameter, e.g. `/MODE yournick +s +ck`. The stream can also be mirrored to
a channel (`chanlog`), a file (`log_json`), or the system logger (`syslog`).
## User & network management
| Command | Purpose |
|---------|---------|
| `KILL <nick> :<reason>` | Disconnect a user from the network. |
| `WALLOPS :<msg>` | Message all `+w` users. |
| `GLOBOPS :<msg>` | Message all opers. |
| `SHUN <mask> [dur] :<reason>` | Silence a user (they stay connected but can't act). |
| `CHECK <nick\|#chan\|mask>` | Deep inspection of a user, channel, or mask. |
| `GEOIP <nick\|ip>` | Country lookup (needs `geoip_database`). |
| `TLINE <mask>` | How many connected users a proposed ban mask would hit. |
## X-lines (bans)
Bans are persisted to disk and survive restarts. Expired entries are purged
automatically.
| Command | Bans by | Scope |
|---------|---------|-------|
| `KLINE <mask> [dur] :<reason>` | user@host | this server |
| `GLINE <mask> [dur] :<reason>` | user@host | whole network |
| `ZLINE <ip> [dur] :<reason>` | IP / CIDR | whole network (cheapest — pre-DNS) |
| `ELINE <mask> [dur] :<reason>` | user@host | exemption from other X-lines |
| `QLINE <mask> [dur] :<reason>` | nick mask | reserve/forbid nicknames |
| `CBAN <#mask> [dur] :<reason>` | channel name | forbid joining/creating |
| `RLINE <regex> [dur] :<reason>` | `nick!user@host realname` regex | native regex engine |
| `TBAN <#chan> <dur> <mask>` | a timed `+b` on one channel | auto-lifts |
Durations accept human forms (`1d`, `2h`, `30m`); `0` or omitted means permanent.
## Override toolbox
Force actions an ordinary user couldn't take. These change a target's identity or
state directly.
| Command | Effect |
|---------|--------|
| `SANICK <nick> <new>` | Force a nick change. |
| `SAJOIN <nick> <#chan>` / `SAPART` | Force join / part. |
| `SAKICK <#chan> <nick>` | Force a kick. |
| `SAMODE <target> <modes>` | Set modes with server authority. |
| `SATOPIC <#chan> :<topic>` | Force a topic. |
| `SAQUIT <nick> :<reason>` | Force a quit. |
| `CHGHOST` / `CHGIDENT` / `CHGNAME` | Change a user's displayed host / ident / real name. |
| `SETHOST` / `SETIDENT` / `SETNAME` | Change your *own* host / ident / real name. |
| `SWHOIS <nick> :<line>` | Add a custom WHOIS line to a user. |
| `NICKLOCK` / `NICKUNLOCK` | Freeze / release a user's nick. |
| `CLEARCHAN <#chan>` | Clear a channel (kick everyone / reset it). |
| `SETIDLE <secs>` | Adjust your reported idle time. |
## Services-side commands
These are the interface a linked services package drives (see
[linking](linking.md)): `SVSNICK`, `SVSJOIN`, `SVSPART`, `SVSMODE`, `SVSLOGIN`,
`SVSLOGOUT`, plus `SVSHOLD` / `SVSTOPIC` / `SVSOPER` / `SVSCMODE` and generic
`ENCAP` / `METADATA`.
## Server management
| Command | Effect |
|---------|--------|
| `REHASH` | Re-read the config and apply every setting that can change at runtime. |
| `CONNECT <server>` | Dial a configured uplink. |
| `DIE` / `RESTART` | Shut down / restart the daemon. |
| `MAP` / `LINKS` | Show the network topology (hideable from non-opers). |
## Diagnostics
`STATS <char>`, `SSLINFO <nick>` (TLS/cert details), `REPUTATION <nick\|ip>`,
`SECURITYGROUPS`, and `FILTER` (manage spam/word filters at runtime).