Files
vnctalk-prosody/PATCHES_AND_MODULES.md
Stefan-Sanger 2046867241 docs: add AGENTS.md, patch analysis, and pytest integration suite
- Add AGENTS.md with repo-specific conventions, build steps, and quirks
- Add PATCHES_AND_MODULES.md documenting every upstream deviation
- Add tests/ with pytest/slixmpp integration suite for core, MUC,
  vnctalk extensions, and infrastructure verification
- Include pytest.ini and .gitignore

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
2026-07-15 17:58:02 +02:00

269 lines
14 KiB
Markdown

# VNCtalk Prosody Patches & Custom Modules Analysis
This document describes how the VNCtalk Prosody distribution deviates from upstream Prosody 0.11.6. The deviations are delivered as:
- **`patches/`** — direct replacements for upstream core files
- **`vnctalk/`** — additional custom modules
> **Note:** `patches.list` is stale and unused. The Dockerfile hard-copies each patch in a `RUN cp …` block.
---
## 1. Core Patches (`patches/`)
### 1.1 `mod_mam.lua` → `modules/mod_mam/mod_mam.lua`
**Purpose:** Message Archive Management (XEP-0313) for 1:1 chats.
**Key deviations from upstream:**
- **`vnc-rest-message` hook support:** Added `vnc_message_handler(event)` hooked on `vnc-rest-message` (priority 0). This allows the REST injection module (`mod_http_rest`) to archive messages that are injected via HTTP.
- **Store-user logic for REST:** When `vnc_rest` is true, `store_user` is derived from `orig_from` instead of `orig_to`, so injected stanzas are archived under the sender's archive.
- **`shall_store` always returns true:** The function `shall_store(user, who)` unconditionally returns `true`, bypassing any roster-based archive filtering.
- **Archives normal messages with body:** The condition `orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body"))` is kept but the implementation now also processes `vnc-rest-message` events.
**Behavioural contract:**
- Any message injected via `/rest` (see `mod_http_rest`) must be archived under the sender's MAM archive.
- MAM queries must return `stanza-id` elements and support RSM pagination.
---
### 1.2 `mod_muc.lua` → `modules/muc/mod_muc.lua`
**Purpose:** MUC component loader and room lifecycle management.
**Key deviations from upstream:**
- **Custom unregister IQ hook:** Added `iq-set/bare/xmpp:vnctalk:unregister:query``handle_unregister_iq`. This exposes a VNCtalk-specific room-unregistration endpoint.
- **Debug helper:** Added `dumpTable(t, depth)` utility function.
- **Room defaults:** `muc_room_default_public` defaults to `false` (rooms are hidden by default).
---
### 1.3 `mod_muc_mam.lua` → `modules/mod_muc_mam.lua`
**Purpose:** MUC Message Archive Management.
**Key deviations from upstream:**
- **`with` filter hardcoded to `message<groupchat`:** The archive query and history loader both set `with = "message<groupchat"`, meaning only groupchat messages are stored/retrieved; presence stanzas are ignored unless `muc_log_presences` is enabled.
- **Presence archiving option:** If `muc_log_presences` is true, join/leave presences are archived with synthetic `with` values (`presence` / `presence<unavailable`).
- **MUC history from archive:** The `muc-get-history` hook loads history from the archive backend when the in-memory `_history` buffer is insufficient, respecting `max_history_messages`.
- **Stanza-id stripping before broadcast:** A `muc-broadcast-message` hook (priority 1) strips any `stanza-id` tags that claim to be from the room JID before the stanza is broadcast.
- **Occupant affiliation injection:** When `whois == "anyone"`, archived messages include an `<x xmlns="http://jabber.org/protocol/muc#user">` item with the sender's affiliation and role.
---
### 1.4 `mod_muc_unique.lua` → `modules/mod_muc_unique.lua`
**Purpose:** XEP-0307 Unique Room Names.
**Key deviations from upstream:**
- **Bare-JID request returns error:** Added `handle_iq_tobare` that replies with `item-not-found` for IQ-get requests sent to a bare JID, rather than generating a unique name. Unique names are only served for host-targeted requests.
---
### 1.5 `moduleapi.lua` → `core/moduleapi.lua`
**Purpose:** Module API base.
**Key deviations from upstream:**
- **Added `open_host_store`:** New method `api:open_host_store(host, name, store_type)` allows a module to open a data store on behalf of a different host. This is heavily used by MUC FCM modules to read user private data / vCards from the main virtual host while running inside the MUC component.
---
### 1.6 `muc.lib.lua` → `modules/muc/muc.lib.lua`
**Purpose:** MUC room implementation (the largest patch).
**Key deviations from upstream:**
- **Offline-affiliate broadcast (`broadcast` method):** After broadcasting to all online occupants, the method iterates over `_affiliations` and sends the stanza to any affiliated JIDs that are **not** currently in the room, **provided their domain is not hosted locally**. This enables federation/remote-user delivery.
- **`publicise_occupant_status` skip for unavailable:** When an occupant's role becomes `nil` (they left), the code logs `"skipping unavailable presence"` and does **not** route an unavailable presence to the leaving user themselves. This changes the standard XEP-0045 self-presence delivery.
- **Debug helpers:** Added `dumpTable` and `table_clone`.
---
### 1.7 `hidden.lib.lua` → `modules/muc/hidden.lib.lua`
**Purpose:** Room visibility (public/hidden).
**Key deviations from upstream:**
- **Restricted public rooms:** If `muc_room_allow_public` is `false` (default in VNCtalk config), the public-room config option is hidden from non-admins, and only admins may create public rooms.
---
### 1.8 `register.lib.lua` → `modules/muc/register.lib.lua`
**Purpose:** MUC room nickname registration.
**Key deviations from upstream:**
- **VNCtalk unregister handler (`handle_unregister_iq`):** Fires `vnc-muc-kick` event and unconditionally removes the user's affiliation from `_affiliations`, bypassing normal affiliation-change logic. This is used for VNCtalk-specific account deletion/room cleanup.
---
### 1.9 `mod_carbons.lua` → `modules/mod_carbons.lua`
**Purpose:** XEP-0280 Message Carbons.
**Key deviations from upstream:**
- **`vnc-rest-message` hook:** Added `module:hook("vnc-rest-message", c2s_message_handler, -0.5)`. Messages injected via the REST endpoint are carbon-copied to the user's other resources just like locally-sent messages.
---
### 1.10 `mod_admin_telnet.lua` → `modules/mod_admin_telnet.lua`
**Purpose:** Admin telnet console.
**Key deviations from upstream:**
- **Listens on all interfaces:** Changed `interface` from `"127.0.0.1"` to `"*"`. The healthcheck script (`healthcheck.sh`) relies on connecting to `127.0.0.1:5582`, but the console is now bound to `0.0.0.0`.
---
### 1.11 `portmanager.lua` → `core/portmanager.lua`
**Purpose:** Network port management.
**Key deviations from upstream:**
- **Respects `network_default_read_size` for socket mode:** `local default_mode = config.get("*", "network_default_read_size") or 4096;` instead of a hardcoded `4096`. The VNCtalk config sets this to `8192` to support larger stanzas (Jitsi/file transfers).
---
## 2. Custom Modules (`vnctalk/`)
### 2.1 Authentication
#### `mod_auth_http_async`
- **HTTP-based authentication.** Replaces Prosody's internal password database with an async HTTP call to `http_auth_url`.
- Sends a `Basic` auth header with `base64(username@host:password)`.
- If `util.async` is unavailable, falls back to synchronous `socket.http` / `ssl.https`.
- `user_exists` always returns `true`; `set_password` / `create_user` / `delete_user` are no-ops.
---
### 2.2 Push Notifications (FCM)
#### `mod_vnc_fcm`
- **FCM push for 1:1 messages.** Hooks on `message/bare`, `pre-message/bare/full`, and `vnc-rest-message`.
- Reads FCM tokens from private storage (`documents:stanza:io:json``fcm` element) and from a map store (`fcmtoken`).
- Supports iOS and Android tokens; iOS gets a notification payload with sound, Android gets data-only.
- Handles VNCtalk-specific extensions: `vncTalkConference`, `whiteboard`, `attachment`, `read` signals, Jitsi URL/room.
- Removes invalid FCM tokens (`NotRegistered`, `InvalidRegistration`, etc.) from user private data automatically.
- **Inactive-device tracking:** Uses `csi-client-active/inactive` hooks to avoid pushing to devices that are online but inactive.
#### `mod_vnc_fcm_hin`
- Variant of `mod_vnc_fcm` with HIN (German health network) specific modifications.
#### `mod_vnc_muc_fcm` / `mod_vnc_muc_fcm_hin`
- **FCM push for MUC messages.** Loaded on the MUC component.
- Opens stores against `storage_host` (the main virtual host) using `module:open_host_store`.
- On each groupchat message, iterates over room affiliations and pushes to offline/non-occupant members.
---
### 2.3 MUC Extensions
#### `mod_vnc_muc_automember`
- Automatically grants `member` affiliation to anyone who receives a MUC invite, if they were previously unaffiliated.
#### `mod_vnc_muc_hook`
- Sends a XMPP message notification (with `http://vnc.biz/xmpp/muc#hook` namespace) to online users who are affiliated with a room but not currently joined, when an "important" message (has non-empty body) is broadcast.
- Optionally sends a mediated invite instead of a plain notification (`muc_notification_invite`).
#### `mod_vnc_muc_data`
- Adds a `muc#roomconfig_vdata` config field and `muc#roominfo_vdata` disco info field.
- On config change, broadcasts a groupchat message with `<x xmlns='xmpp:vnctalk:update'/>` containing JSON-encoded affiliations and room data.
#### `mod_vnc_e2ehints`
- Adds a `muc#roomconfig_e2e` boolean field and `muc#roominfo_e2e` disco info field for end-to-end encryption hints.
#### `mod_vnc_remotemucstore`
- Archives groupchat messages from **remote** MUCs (federation) into a local archive (`muc_remote`) so users can query history even for rooms not hosted locally.
- Stores MUC presence metadata (real JID mapping, subject, nick) in a map store.
- Prevents further processing of messages addressed to bare JIDs from remote MUCs (so they don't create offline message spam).
#### `mod_vnc_remotemucinvite`
- Archives mediated MUC invitations sent to remote users in a store (`muc_remote_inv`).
#### `mod_vnc_track_kicks`
- Tracks kick/ban events in MUC rooms (lightweight event logger).
---
### 2.4 vCard & Avatar
#### `mod_vnc_vcard_avatar`
- On vCard update (IQ-set to self), extracts the PHOTO binval, decodes it, and uploads it via HTTP PUT to `avatar_upload_url`.
- Uses Basic auth if `avatar_upload_user` / `avatar_upload_pass` are configured.
- Stores an `avatarupdate` timestamp in a map store.
#### `mod_vnc_vcard_fallback`
- Intercepts vCard IQ-get requests.
- If the user has no vCard on file, auto-generates one from the username (dot-separated usernames become `FIRSTNAME LASTNAME`).
- Adds `ORGNAME` if `default_vcard_orgname` is configured.
---
### 2.5 Messaging Extensions
#### `mod_vnc_timestamp`
- Adds a custom `<stamp xmlns='xmpp:vnctalk:stamp'/>` tag to incoming chat/groupchat messages that have a body.
- Sends an IQ-result back to the sender containing the timestamp and original stanza ID.
#### `mod_vnc_receipts`
- Archives XEP-0184 delivery receipts (`<received xmlns='urn:xmpp:receipts'/>`) in an archive store (`receipts`).
#### `mod_vnc_lastactivity`
- Extends XEP-0012 last activity with avatar hash caching.
- Caches SHA1 of the user's vCard PHOTO in memory (`avt_hash_table`).
- Stores/retrieves remote user activity and avatar updates in map stores.
#### `mod_vnc_broadcast`
- Implements a `vnc_broadcast` component.
- Accepts messages with `<vncTalkBroadcast xmlns='xmpp:vnctalk'>` and fans them out to:
- Explicit `to` JIDs
- Roster groups (via `roster_manager.load_roster`)
- MUC room affiliations (if a `to` JID is a MUC)
---
### 2.6 HTTP / REST
#### `mod_http_rest`
- Exposes `POST /rest` on the HTTP server.
- Accepts `Content-Type: text/xml` bodies, parses them as XMPP stanzas, and injects them into the server via `module:fire_event("vnc-rest-message", …)`.
- Returns `201` on success, `415` for wrong content type, `422` for unparseable body.
#### `mod_http_upload_external`
- Delegates HTTP file upload to an external PHP endpoint (`share.php`).
- Generates signed URLs using `http_upload_external_secret`.
---
### 2.7 Utility Modules
| Module | Purpose |
|--------|---------|
| `mod_alias` | Allows users to have alias JIDs |
| `mod_roster_command` | Ad-hoc commands for roster management |
| `mod_s2s_keepalive` | Keepalive pings for S2S connections (Jitsi compatibility) |
| `mod_smacks` / `mod_smacks_offline` | Stream Management (XEP-0198) with offline queue support |
| `mod_carbons_copies` | Helper for carbons routing |
| `mod_log_slow_events` | Logs events exceeding `log_slow_events_threshold` |
| `mod_discoitems` | Manual override of disco items for a host |
| `mod_filter_chatstates` | Drops chat-state notifications under certain conditions |
| `mod_idlecompat` | Compatibility shim for idle detection |
| `mod_http_altconnect` | Alternative connection methods discovery |
| `mod_http_index` | Static HTTP index page |
| `mod_webpresence` | Publish presence as web images/icons |
| `mod_auto_accept_subscriptions` | Auto-accept presence subscriptions |
| `mod_vcard_muc` | vCard support for MUC rooms |
| `mod_vnc_delfile` | File deletion command via XMPP |
---
## 3. Configuration-Driven Behaviour
Several behaviours are not hard-coded in Lua but emerge from `config/prosody.cfg.lua.template`:
| Config Option | Effect |
|---------------|--------|
| `authentication = "http_async"` | All client auth goes to `hybridaAuthUrl` |
| `default_archive_policy = "roster"` | MAM archives only roster contacts by default |
| `muc_log_by_default = true` / `muc_log_all_rooms = true` | All MUC messages archived |
| `c2s_require_encryption = true` | Plaintext C2S rejected |
| `keepalive_servers = { "${DEFAULT_JITSI_CONFERENCE}" }` | S2S keepalive pings sent to Jitsi |
| `component_secret = "…"` | Fixed MUC component secret (not randomly generated) |
---
## 4. Healthcheck & Runtime Quirks
- `healthcheck.sh` exits **2** (not 1) when the TLS cert in `/etc/tls-update/tls.crt` differs from the loaded one. Orchestrators must treat exit code 2 as a restart signal.
- `startup-sidecar.sh` runs a Node `http-server` on port 8080 for static files; this is **not** the XMPP server.
- `startup.sh` generates fallback certs from base64 defaults if `prosodySSLkey/cert` env vars are missing.