diff --git a/README.md b/README.md index 1ebf924..72c1304 100644 --- a/README.md +++ b/README.md @@ -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>` 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** — - **Issues** — +- **Documentation** — [`docs/`](docs/) - **Config reference** — [`echoircd.conf.example`](echoircd.conf.example) ## License diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f6fb171 --- /dev/null +++ b/docs/README.md @@ -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. diff --git a/docs/anti-abuse.md b/docs/anti-abuse.md new file mode 100644 index 0000000..9b44703 --- /dev/null +++ b/docs/anti-abuse.md @@ -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 = `** 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:` + 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:` 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). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..396d821 --- /dev/null +++ b/docs/architecture.md @@ -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>`, 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). diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 0000000..f1452eb --- /dev/null +++ b/docs/building.md @@ -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 `. + +## 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. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..485ab4d --- /dev/null +++ b/docs/configuration.md @@ -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 = 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=`, `deny=yes`, `parent=` (inherit), +`requiressl=yes|trusted`, `password=` + `hash=`, `port=`, +`localmax=` (per-IP, this server), `globalmax=` (per-IP, network-wide), +`limit=` (total users in class), `maxchans=`, `pingfreq=`, +`timeout=` (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: 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 = [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 = [autoconnect]` | A peer server; password is a shared secret. | +| `sasl_server` | The linked server that handles SASL (relayed AUTHENTICATE). Unset disables SASL. | +| `webirc = [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 = ` | 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 = [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 = ` | 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 = ` / `hidelist = ` | 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:` extban, `GEOIP` command, WHOIS country. | +| `reputation_*` / `reputationexpire` | Per-address reputation scoring and the `y:` extban. | +| `securitygroup = [criteria…]` | A named user set usable as the `g:` 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 = ` | 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). diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..880af67 --- /dev/null +++ b/docs/deployment.md @@ -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 = ` | 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. diff --git a/docs/ircv3.md b/docs/ircv3.md new file mode 100644 index 0000000..43bf263 --- /dev/null +++ b/docs/ircv3.md @@ -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`. diff --git a/docs/linking.md b/docs/linking.md new file mode 100644 index 0000000..56900aa --- /dev/null +++ b/docs/linking.md @@ -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 = [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:` / 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 = [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. diff --git a/docs/modes.md b/docs/modes.md new file mode 100644 index 0000000..6d9050e --- /dev/null +++ b/docs/modes.md @@ -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 :` 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 ` | Channel key; must be supplied to join. | + +**Type C — a parameter only when setting:** + +| Mode | Meaning | +|------|---------| +| `+l ` | Member limit. | +| `+f [*]:` | Message flood: kick past `lines` messages in `secs`; a leading `*` also bans. | +| `+j :` | Join flood throttle. | +| `+F :` | Nick-change flood throttle. | +| `+L <#target>` | Redirect joiners here when the channel is full/keyed/invite-only. | +| `+H :` | Replay recent messages to joiners (in-channel history). | +| `+B ` | Anti-caps: block messages that are more than `percent` uppercase. | +| `+J ` | Block rejoin for `secs` after a kick. | +| `+d ` | New joiners can't speak for `secs`. | +| `+K ` | 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:` | members of a named [security group](configuration.md). | +| `y:` | [reputation](configuration.md) score, e.g. `y:<100` (below 100), `y:>500`. | +| `r:` | real name (GECOS). | +| `j:<#chan>` | users who are also in `#chan`. | +| `s:` | the server a user is on. | +| `G:` | 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:` | muted — can't speak (but stay joined). | +| `c:` | can't send formatting/colour. | +| `n:` | 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 +``` diff --git a/docs/operators.md b/docs/operators.md new file mode 100644 index 0000000..e9f88ef --- /dev/null +++ b/docs/operators.md @@ -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: # or a hashed password +oper = root $2b$12$… # bcrypt is supported too +``` + +A client authenticates with `/OPER `, gains user mode `+o`, and +(optionally) a staff prefix — see [operprefix](configuration.md). Generate a +hashed password with the oper-only `/MKPASSWD ` 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 = +`). 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 :` | Disconnect a user from the network. | +| `WALLOPS :` | Message all `+w` users. | +| `GLOBOPS :` | Message all opers. | +| `SHUN [dur] :` | Silence a user (they stay connected but can't act). | +| `CHECK ` | Deep inspection of a user, channel, or mask. | +| `GEOIP ` | Country lookup (needs `geoip_database`). | +| `TLINE ` | 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 [dur] :` | user@host | this server | +| `GLINE [dur] :` | user@host | whole network | +| `ZLINE [dur] :` | IP / CIDR | whole network (cheapest — pre-DNS) | +| `ELINE [dur] :` | user@host | exemption from other X-lines | +| `QLINE [dur] :` | nick mask | reserve/forbid nicknames | +| `CBAN <#mask> [dur] :` | channel name | forbid joining/creating | +| `RLINE [dur] :` | `nick!user@host realname` regex | native regex engine | +| `TBAN <#chan> ` | 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 ` | Force a nick change. | +| `SAJOIN <#chan>` / `SAPART` | Force join / part. | +| `SAKICK <#chan> ` | Force a kick. | +| `SAMODE ` | Set modes with server authority. | +| `SATOPIC <#chan> :` | Force a topic. | +| `SAQUIT :` | 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 :` | 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 ` | 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 ` | Dial a configured uplink. | +| `DIE` / `RESTART` | Shut down / restart the daemon. | +| `MAP` / `LINKS` | Show the network topology (hideable from non-opers). | + +## Diagnostics + +`STATS `, `SSLINFO ` (TLS/cert details), `REPUTATION `, +`SECURITYGROUPS`, and `FILTER` (manage spam/word filters at runtime).