1 Commits
Author SHA1 Message Date
clawbot 24d0900dab Bring README.md in line with the modes and the schema (closes #119)
check / check (push) Canceled after 0s
The MODE section described a query-only command. It now covers channel
and user mode queries and changes on the HTTP API and the IRC listener,
the letters each accepts, what is broadcast, and the error numerics.
The schema section now lists every table and column in
001_initial.sql.

Every other statement this change touches was checked against the code:
polled numerics carry their name in command and their number in code;
command errors are numerics with HTTP 200; the broker is keyed by
session; WAL is never on; a hostname falls back to the IP and is
public; and the IRC listener handles modes, NOTICE, INVITE, +s and +H
more narrowly, with gaps in its bridge to the HTTP API.

Model: opus-5-5
2026-10-08 08:42:57 +00:00
+54 -33
View File
@@ -216,25 +216,27 @@ Each session has an IRC-style hostmask composed of three parts:
- **nick** — the user's current nick (changes with `NICK` command)
- **username** — an ident-like identifier set at session creation (optional
`username` field in the session request; defaults to the nick)
- **hostname** — automatically resolved via reverse DNS of the connecting
client's IP address at session creation time
- **hostname** — the reverse DNS name of the creating client's IP address,
looked up at session creation time, or the IP address itself when the lookup
gives no name
- **ip** — the real IP address of the session creator, extracted from
`X-Forwarded-For`, `X-Real-IP`, or `RemoteAddr`
Each **client connection** (created at session creation or login) also stores
its own **ip** and **hostname**, allowing the server to track the network origin
of each individual client independently from the session. Client-level IP and
hostname are **not displayed to regular users**. They are only visible to
**server operators** (o-line) via `RPL_WHOISACTUALLY` (338) when the oper
performs a WHOIS on a user.
its own **ip** and **hostname**, found the same way. The session's hostname is
the hostname of the client that created it, and every user can see it: in WHOIS
(311), WHO (352), USERHOST (302), NAMES (353) over the HTTP API, and
`GET /api/v1/channels/{name}/members`. Only `RPL_WHOISACTUALLY` (338) is for
**server operators** (o-line) alone: it gives the IP address and hostname of the
target's newest client.
The hostmask appears in:
- **WHOIS** (`311 RPL_WHOISUSER`) — `params` contains
`[nick, username, hostname, "*"]`
- **WHOIS (oper-only)** (`338 RPL_WHOISACTUALLY`) — when the querier is a server
operator, includes the target's current client IP and hostname (HTTP API only;
the IRC listener's WHOIS does not send it)
operator, includes the IP address and hostname of the target's newest client
(HTTP API only; the IRC listener's WHOIS does not send it)
- **WHO** (`352 RPL_WHOREPLY`) — `params` contains
`[channel, username, hostname, server, nick, flags]`
@@ -438,7 +440,8 @@ The entire read/write loop for a client is two endpoints. Everything else
```
┌─ Client ──────────────────────────────────────────────────┐
│ │
│ 1. POST /api/v1/session {"nick":"alice"} │
│ 1. POST /api/v1/session │
│ {"nick":"alice","pow_token":"<hashcash stamp>"} │
│ → Set-Cookie: neoirc_auth=<random_hex>; HttpOnly; ... │
│ → {"id":1, "nick":"alice"} │
│ │
@@ -471,7 +474,8 @@ The entire read/write loop for a client is two endpoints. Everything else
```
┌─ Client A ────────────────────────────────────────────────┐
│ │
│ 1. POST /api/v1/session {"nick":"alice"} │
│ 1. POST /api/v1/session │
│ {"nick":"alice","pow_token":"<hashcash stamp>"} │
│ → Set-Cookie: neoirc_auth=<cookie_a>; HttpOnly; ... │
│ → {"id":1, "nick":"alice"} │
│ │
@@ -1079,8 +1083,8 @@ RPL_WHOISCHANNELS (319), and RPL_ENDOFWHOIS (318).
If the querying user is a **server operator** (authenticated via `OPER`), the
response over the HTTP API additionally includes RPL_WHOISACTUALLY (338) with
the target's current client IP address and hostname. The IRC listener's WHOIS
does not send it.
the IP address and hostname of the target's newest client. The IRC listener's
WHOIS does not send it.
**C2S:**
@@ -1122,7 +1126,8 @@ LUSERS replies are also sent automatically during connection registration.
Authenticate as a server operator (o-line). On success, the session gains oper
privileges: it can use `KILL` and `WALLOPS`, and over the HTTP API its WHOIS
responses include more (the target user's current client IP and hostname).
responses include more (the IP address and hostname of the target user's newest
client).
**C2S:**
@@ -1346,10 +1351,10 @@ key ERR_BADCHANNELKEY (475), and one joining a full channel ERR_CHANNELISFULL
`KICK #channel nick [:reason]`. The kicked user and all channel members receive
the KICK message.
**NOTICE:** Follows RFC 2812 over the HTTP API — NOTICE never triggers
auto-replies (including RPL_AWAY), and skips hashcash validation on +H channels
(servers and services use NOTICE). On the IRC listener, a NOTICE to a user who
is away does get RPL_AWAY.
**NOTICE:** Over the HTTP API, a NOTICE gets no RPL_AWAY and no hashcash check
on `+H` channels, but it gets the same error numerics as PRIVMSG (411, 412, 401,
403, 404). The IRC listener handles NOTICE exactly as PRIVMSG, so a NOTICE to a
user who is away gets RPL_AWAY there.
**ISUPPORT:** The server advertises `PREFIX=(ov)@+` in RPL_ISUPPORT (005), with
`CHANMODES=b,k,Hl,imnst` over the HTTP API and `CHANMODES=,,H,imnst` on the IRC
@@ -1448,9 +1453,11 @@ difficulty is advertised via `GET /api/v1/server` in the `hashcash_bits` field.
| `pow_token` | string | Conditional | Hashcash stamp (required when server has `hashcash_bits` > 0) |
The `username` field sets the user portion of the IRC hostmask
(`nick!user@host`). The hostname is automatically resolved via reverse DNS of
the connecting client's IP address at session creation time. Together these form
the hostmask used in WHOIS, WHO, and ban matching (`+b`).
(`nick!user@host`). The hostname is the reverse DNS name of the connecting
client's IP address, looked up at session creation time, or the IP address
itself when the lookup gives no name; every user can see it (see
[Hostmask](#hostmask-nickuserhost)). Together these form the hostmask used in
WHOIS, WHO, NAMES, and ban matching (`+b`).
**Response:** `201 Created`
@@ -1729,12 +1736,12 @@ reference with all required and optional fields.
All IRC commands return HTTP 200 OK, usually with `{"status": "error"}` when the
reply is an error numeric (a WHOIS for an unknown nick still gets
`{"status": "ok"}`), and otherwise a status of the command's own (see each
command). IRC-level success and error responses are delivered as **numeric
replies** through the message queue (see
[Numeric Reply Codes](#numeric-reply-codes-s2c-only)); `PING` is the exception,
its `PONG` is the HTTP response body. HTTP error codes (4xx/5xx) are reserved
for transport-level problems: malformed JSON (400), missing/invalid auth cookies
(401), and server errors (500).
command). **Numeric replies**, for success and for errors, are delivered through
the message queue (see [Numeric Reply Codes](#numeric-reply-codes-s2c-only)); a
command that has none, such as a successful `PRIVMSG`, is answered by the HTTP
response alone, and `PING`'s `PONG` is the HTTP response body. HTTP error codes
(4xx/5xx) are reserved for transport-level problems: malformed JSON (400),
missing/invalid auth cookies (401), and server errors (500).
**HTTP errors (transport-level only):**
@@ -2402,12 +2409,12 @@ applied; `001_initial.sql` creates the tables below.
#### `sessions`
| Column | Type | Description |
| --------------- | -------- | ------------------------------------------------------------- |
| --------------- | -------- | ------------------------------------------------------------------------------- |
| `id` | INTEGER | Primary key (auto-increment) |
| `uuid` | TEXT | Unique session UUID |
| `nick` | TEXT | Unique nick |
| `username` | TEXT | IRC ident/username portion of the hostmask (defaults to nick) |
| `hostname` | TEXT | Reverse DNS hostname of the connecting client IP |
| `hostname` | TEXT | Reverse DNS name of the creating client's IP, or the IP itself when it has none |
| `ip` | TEXT | Real IP address of the session creator |
| `is_oper` | INTEGER | Server operator, user mode `+o` (0 = no, 1 = yes) |
| `is_wallops` | INTEGER | User mode `+w`, receives `WALLOPS` (0 = no, 1 = yes) |
@@ -2422,13 +2429,13 @@ Index on `(uuid)`.
#### `clients`
| Column | Type | Description |
| ------------ | -------- | ---------------------------------------------------------- |
| ------------ | -------- | ----------------------------------------------------------------------- |
| `id` | INTEGER | Primary key (auto-increment) |
| `uuid` | TEXT | Unique client UUID |
| `session_id` | INTEGER | FK → sessions.id (cascade delete) |
| `token` | TEXT | Auth cookie value (SHA-256 hash of the 64-hex-char cookie) |
| `ip` | TEXT | Real IP address of this client connection |
| `hostname` | TEXT | Reverse DNS hostname of this client connection |
| `hostname` | TEXT | Reverse DNS name of this client's IP, or the IP itself when it has none |
| `created_at` | DATETIME | Client creation time |
| `last_seen` | DATETIME | Last authenticated HTTP API request (or creation) |
@@ -2671,8 +2678,22 @@ IRC_LISTEN_ADDR: ""
Messages sent by IRC clients appear in channels visible to HTTP/JSON API clients
and vice versa. The IRC listener and HTTP API share the same database, broker,
and session infrastructure. A user connected via IRC and a user connected via
the HTTP API can communicate in the same channels seamlessly.
and session infrastructure, so a user connected via IRC and a user connected via
the HTTP API can talk in the same channels, with these gaps today:
- An IRC listener user receives only the first line of a multi-line `body`.
- A `+k`, `+l` or `+b` change made over the HTTP API reaches IRC listener
members without its key, limit or mask.
- The IRC listener's `JOIN` accepts any channel name, while over the HTTP API a
name that is not `#` followed by 1–63 letters, digits, `_` or `-` gets
ERR_NOSUCHCHANNEL (403), so HTTP API users cannot join such a channel.
- The IRC listener does not pass on a `PRIVMSG`, `NOTICE`, `JOIN`, `PART`,
`NICK` or `QUIT` whose sender's nick matches the user's own nick ignoring
case, so an IRC listener user `alice` never sees these from `Alice`, although
nicks are case-sensitive.
https://git.eeqj.de/sneak/neoirc/issues/127 tracks the first three and
https://git.eeqj.de/sneak/neoirc/issues/128 the last.
### Docker Usage