You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Summary of Changes
Messaging
Date display preferences: users and sysops can now choose between relative timestamps ("4d ago") and exact date/time for message lists and headers, and choose whether echomail is ordered and displayed by received date or written date.
Message search scoped by network and interest: searching for messages from the Echo Areas page now respects the network and interest filters selected there, and searching while browsing a single interest on the Echomail page now stays within that interest's echo areas, instead of always searching every echo area.
Fixed: inbound echomail landed in areas with an empty domain: the network domain for incoming echomail is now taken from the uplink that delivered the packet, instead of only from the message author's address. Authors outside the uplink's routing patterns no longer produce messages with an empty domain.
Echomail no longer sent through another network's uplink: when an echo area's network has no uplink configured, its outbound echomail used to be sent through the default uplink, which belongs to a different network. It now stays local and a warning is logged.
Inbound echomail refused when it would land in a local or ambiguous area: incoming echomail is now dropped, with a log line, instead of being filed in an area that is flagged local, in an area with no network domain, or in one of several areas whose domains differ only by letter case.
AreaFix / FileFix
Structural reply parsing across more hub mailers: AreaFix and FileFix replies are now parsed by recognizing the concrete layout each hub mailer actually sends — Mystic BBS/MBSE command blocks, delimited and columnar tables, BBBS/Li6-style quoted address lists, and HPT-style flag-prefixed quoted lists — instead of scanning for keywords. Real echo areas with common names such as LINUX, WINDOWS, or BASE are no longer mistaken for header text or help output.
Mandatory preview before syncing areas: clicking "Sync Areas to Local BBS" (from the latest reply, or from any individual incoming message in the Message History table) now shows a preview of exactly which areas will be created, reactivated, deactivated, or left unchanged. Nothing is written to the database until this preview is explicitly confirmed.
Data-driven grammar definitions: a new Admin -> Area Management -> AreaFix Grammars page lets a sysop teach AreaFix a new hub reply format without a code change, either by hand or by pasting a sample reply and asking the built-in AI assistant to suggest one. Suggestions are always added disabled for review before saving.
Per-uplink format memory: BinktermPHP now remembers which reply format last matched each hub's confirmed sync, tries that format first on the hub's next reply, and flags it on the preview screen if the format changes unexpectedly. The remembered format for each uplink can be viewed, forced, or cleared from Admin -> BBS Settings -> BinkP Uplinks -> Edit Uplink.
Automatic area sync on reply is now opt-in: receiving an AreaFix/FileFix reply from a hub that looks like an area list no longer automatically creates or activates local echo areas / file areas by default. Set AREAFIX_AUTOIMPORT_ENABLED=true in .env to restore the previous automatic behavior.
Fixed: AreaFix sync set an override address on echo areas: syncing areas from a hub's AreaFix reply filled in the echo area's Uplink Address ("Override Uplink FidoNet address") field on every area it created or touched. The sync no longer sets it, and deactivating areas missing from the hub's list is now scoped by network domain and tag instead of by that address.
BinkP
Slow peers no longer stall frame reads: a BinkP peer that sends a frame in pieces, with a pause between them, used to hold the session in a single read for the whole BinkP timeout. Each polling read now takes only the bytes that have arrived and picks the frame up where it left off on the next pass.
Sessions that end abnormally are reported as failed: a BinkP session that ended on the hard EOB or inactivity timeout, or because the peer disconnected before the EOB exchange finished, was logged and reported as successful. It is now recorded as failed.
Fixed: wrong packet password stopped all inbound processing: one inbound packet with the wrong packet password raised an uncaught error that aborted the whole inbound run, and the packet was tried again, with the same result, on every later run. The packet is now rejected and recorded as an error, and processing continues with the next packet.
Fixed: process_packets.php lock file errors reported as "already running": when data/run/process_packets.lock could not be opened, for example because of its owner or permissions, the script logged that another instance was running and exited successfully. It now logs the real error and exits with status 1.
Fixed: inbound connections closed reliably: the BinkP server did not reliably release an accepted connection when a session failed, and an error while sending the "maximum connections" busy reply could escape into the accept loop. Every accepted connection is now closed exactly once, and a failure while handling one can no longer affect the server process.
Scheduled polls run one at a time, and never overlap on the same host: the scheduler now waits for each poll to finish and processes the packets it received before starting the next one. Outbound sessions to the same host and port are also serialized, so uplinks on different networks that share a hub no longer collide. A poll that finds the host busy is deferred, not failed.
Fixed: mail posted during a scheduled poll waited up to 30 minutes: a message queued while a scheduled poll was running, or just after it, was skipped as already delivered and then held back by the outbound queue timer. The scheduler now polls again straight away when new outbound files appeared after the scheduled poll started.
Administration
Fixed scripts/user-manager.php create: the operator CLI's create command failed on PostgreSQL with column "is_active" is of type boolean but expression is of type integer, because it inserted the literal 1 instead of a boolean. This is now fixed.
Log rotation by size:scripts/logrotate.php has a new --max-size option that rotates only logs that have grown past a size, so the script can run often without touching small logs. Docker installs can set LOGROTATE_MAX_SIZE to use it. The script also gained --logs-dir, and lowering --keep now removes the extra old generations on the next run.
Admin daemon keeps log lines it cannot write: when a process cannot write its own log file and forwards the line to the admin daemon, and the daemon cannot write that file either, the line is now recorded in data/logs/admin_daemon.log as a warning instead of being discarded.
SQL migrations split on real statement boundaries: the upgrade script now finds the end of each statement in a .sql migration by reading the SQL, instead of by pattern matching. A ; or -- inside a quoted string, a function body or a block comment no longer corrupts the migration. Every migration shipped with BinktermPHP is split exactly as before.
Docker: telnet client added for the BBSLink door: the container image now includes the telnet package, which the BBSLink native door (bbslinknative) needs. Rebuild the image to get it.
Fixed: each login is recorded exactly once: Telnet and SSH logins were recorded twice, which inflated the total login count and Today's Callers, and credited half of them to the web. PacketBBS logins were not recorded at all and did not update the user's last login time. Each successful login now produces one login event tagged with the service it came in on (web, telnet, ssh or packetbbs).
Downlink queue sorted by owner and shows network domain: the summary table on the Downlink Queue tab of the BinkP status page now lists downlinks grouped by owner, then by address, and shows each downlink's network domain after its address, for example 21:3/236.10@examplenet.
Web Interface
Fresh assets after an upgrade: the service worker now asks the server whether a file has changed before saving it to its cache after an upgrade, so browsers no longer keep an old copy of a CSS or JavaScript file from before the upgrade.
Login page honors "Remember me": the checkbox on the login page was never sent to the server, so every web login got a 30-day session cookie. Leaving it unchecked now gives a cookie that the browser discards when it closes.
Recovery from a stale CSRF token: logging in as the same user somewhere else, in another browser or over Telnet or SSH, used to make every already-open page and terminal session fail with "Invalid CSRF token" until it was reloaded or reconnected. Web pages and terminal sessions now fetch the current token and recover on their own. A new GET /api/auth/csrf-token endpoint supplies the token.
Telnet address on the dashboard: the System Information card now shows the address Telnet users connect to, and shrinks the text to fit on one line.
ANSI avatars: each user can paint or import one 10 by 6 Synchronet-compatible CP437 avatar on their profile. It appears on public profiles and in the terminal profile viewer. A new user_ansi_avatars table is created by the migration.
Fixed: BBS menu shell scripts: pages in the BBS menu shell now load the per-user storage helper and receive the user's settings, and the ANSI.SYS script is no longer loaded twice.
robots.txt added: a robots.txt now asks search engines not to crawl /admin/ or /api/.
Web Doors
Longer browser caching for door assets: icons and screenshots served from /door-assets/ now use Cache-Control: public, max-age=604800, stale-while-revalidate=86400 (up from a 24-hour max-age), plus ETag/Last-Modified conditional requests, so repeat visits reload door pages faster and generate less server load.
RLogin door asset sizes stored in the database: icon and screenshot byte sizes for RLogin doors are now stored alongside the image data instead of being recomputed on every request, reducing memory overhead when serving those assets.
MRC chat loads its libraries from the bundled copies: the MRC web door now loads Bootstrap, jQuery and its icons from the copies bundled with BinktermPHP instead of public CDNs, so it works under a strict Content Security Policy and without access to those CDNs.
DOS doors redraw after the browser reconnects: when a browser's connection to a running DOS door drops and comes back within DOSDOOR_RECONNECT_TIMEOUT, the terminal now shows the current door screen again instead of staying blank until the door next writes.
Mobile touch controls and scaling for door games: DOS and RLogin doors now fit a phone screen, with a docked touch gamepad (arrow pad, Esc, Enter, Space, Backspace, number row and hotkeys), ready-made layouts for some popular games, zoom controls, and a high-contrast color mode that brightens dim colors for phone displays.
Door connections kept alive through proxies: the DOS door bridge now pings every connected browser every 20 seconds (DOSDOOR_WS_HEARTBEAT_INTERVAL_MS), and the DOS and RLogin door pages send their own ping every 25 seconds, so a door sitting at an idle screen is no longer dropped by Cloudflare, nginx, Apache or Caddy. A browser that stops answering is disconnected. The default DOSDOOR_RECONNECT_TIMEOUT is now 90 seconds, up from 30.
DOS door ANSI.SYS screen clear homes the cursor: for DOS, RLogin and CP437 native doors, a clear-screen sequence now also moves the cursor to the top left, as ANSI.SYS does, so doors that rely on that line up correctly.
Door screens flushed before the connection closes: when a door exits, the bridge waits 800 ms before closing the browser connection, so the last screen the door wrote, such as a game-over screen, is shown.
Fixed: door player limits could be exceeded by simultaneous launches: several players launching the same door at the same moment could all pass the door's maximum-players check, or be given the same node number. The check and node assignment are now done as one step. A launch that loses the race for the last slot gets an HTTP 503 "Door at capacity" response.
Fixed: PubTerm native door block graphics: the PubTerm native door now uses CP437 output and an 8-bit Telnet connection, so ANSI block graphics display correctly instead of as replacement characters.
Door category tabs and door display settings: the door games page can now group doors by category (genre) instead of by runtime type, with filter tabs and live counts, and the door editors have a category dropdown. A new Doors tab under Admin -> Appearance & Content controls the grouping, whether DoorParty, BBSLink and A-Net keep their own tabs, and whether the Top Scores leaderboard is shown.
Door admin pages: settings list and runtime settings in the manifest editor: the DOS, Native, JS-DOS and Web door pages under Admin now open on a Settings tab that lists each door, with the raw JSON file moved to a JSON tab. The Door Manifest Editor also gained a Runtime Settings card, so a door's manifest and its enabled state, credit cost and limits are saved together with one Save.
RLogin doors: emulate modem speed: each RLogin door has a new Emulate Modem Speed option that slows the door's output to the door's terminal speed, so text and ANSI art draw at dialup pace in the browser, Telnet and SSH.
Fixed: Hangman and Klondike Solitaire could not save or load: these two web doors call /api/webdoor/save, /api/webdoor/load and /api/webdoor/delete, which did not exist, so their saved games were never stored. Those three endpoints now exist and keep each player's saved game in save slot 0.
Fixed: missing icon requests for the sample doors: the SKEL and 8WAYSL sample DOS doors named an icon.svg that is not included, so each page view logged a 404 for their icon. Their manifests now declare no icon.
MeshCore
Radio settings link on the dashboard: the PacketBBS Nodes card on the main dashboard now includes a "My MeshCore radios" link that opens the MeshCore tab of your user settings, where you manage your radios.
Networks
SysopNet added to the networks list: SysopNet (zone 23, hub 23:1/1), an FTN for sysops run by sysops, is now registered automatically on upgrade, so it appears in Admin -> Networks without being created by hand.
Networks listed alphabetically: the network list in Admin -> Networks and the network dropdown when editing an uplink are now sorted purely by name.
BBS Directory
Geocoding provider failures no longer cached: a failed request to the geocoding provider, such as a timeout or an outage, was stored the same way as a genuine "no match" answer, so the location was never looked up again. Failures are now never cached and are retried on the next run.
Geocoding backfill skips known no-match locations: locations the provider has already said it cannot find no longer take up places in a limited backfill batch, so newer entries further down the directory are no longer starved of coordinates.
Terminal Server
Cursor keys, bracketed paste and lone ESC: Telnet and SSH sessions now understand arrow and navigation keys that terminals send with modifiers or in application cursor-key mode, ignore the markers many terminals put around pasted text, and report a lone ESC press as ESC. Before, these were dropped as terminal reports, typed into the screen as stray characters, or treated as unknown keys.
UTF-8 safe line editing and paste containment: text typed at terminal prompts and input dialogs is now handled one character at a time, so accented and other non-ASCII characters are no longer split or lost, the left and right arrows edit in the middle of a line, and the rest of a multi-line paste is discarded after the first line instead of being run against the next menu or password prompt. CR-LF counts as one Enter.
Nagle's algorithm disabled on Telnet and SSH connections: the Telnet, TLS-Telnet and SSH daemons now turn off Nagle's algorithm on accepted connections, so single keystroke echoes and screen redraws are sent immediately instead of being held back, which was noticeable over higher-latency links. Set TERMINAL_TCP_NODELAY=false in .env to keep Nagle enabled. It needs the PHP sockets extension, and without it the setting is skipped.
SSH connection rate limit and session cap: the SSH daemon now limits new connections per source address (SSH_RATE_LIMIT_MAX, default 5 per SSH_RATE_LIMIT_WINDOW, default 60 seconds) and caps the number of simultaneous sessions (SSH_MAX_CHILDREN, default 32). Rejected connections are closed before the daemon forks or starts the SSH handshake. The Telnet daemon now shares the same rate limiter, so a TELNET_RATE_LIMIT_WINDOW of 0 or less now means 60 seconds instead of no limit.
RLogin clients recognized on the Telnet ports: a client that connects with an RLogin handshake (RFC 1282) is now recognized, answered correctly, and no longer sent Telnet negotiation that appears on its screen as stray characters. Native door games also fall back to the user's alias or real name when no handle or username is available.
Dialup speed simulation for Telnet and SSH: sysops can now pace terminal output at a classic modem speed, so ANSI screens draw line by line instead of appearing at once. It is off by default and is set under Admin → BBS Settings → Terminal Server Settings.
Security
Secure flag on session cookies: the binktermphp_session cookie now sets the Secure flag whenever the site is served over HTTPS, so the cookie is no longer sent over a plain HTTP connection even if one is reachable.
Default terminal registration secret no longer trusted: an unset TERMINAL_REGISTRATION_SECRET, or the published default Chang3Me, is no longer accepted as proof that a request came from the telnet/SSH daemons. Docker installs generate a site-specific secret automatically; other installs must set one in .env.
Failed-login throttle: repeated failed logins are now limited per account and per source IP across the web login, telnet, SSH, FTP, NNTP and QWK HTTP downloads. By default an account allows 5 failures and an IP allows 20 within 15 minutes; once either limit is reached, further attempts fail exactly like a wrong password until the window passes. Set AUTH_LOGIN_USER_MAX, AUTH_LOGIN_IP_MAX and AUTH_LOGIN_WINDOW in .env to change the limits (see CONFIGURATION.md). The upgrade migration creates the auth_login_attempts table.
Telnet and SSH sessions disconnected when their web session is revoked: a connected Telnet or SSH user, including one playing a door, is now signed out shortly after their web session is revoked, expires, or is deleted by a password reset. Previously the terminal session kept running with full access until the user disconnected.
PacketBBS sessions end when their web session is revoked: a radio node that logged in to PacketBBS stayed authenticated after its linked web session was revoked, expired or deleted, and could keep sending commands and receiving private messages. The next command or bridge poll now clears the node's login and answers Session expired. LOGIN again.. Pending chat notifications are also discarded on QUIT, on LOGIN and on expiry, and stale sender-row cleanup now has its own PACKETBBS_SESSION_RETENTION_SECONDS setting.
Docker: config JSON files no longer world-readable: the container now restricts the top-level config/*.json files, which hold uplink passwords and API keys, to the owner and group at startup. Previously they were readable by any user in the container.
BinkP debug logging no longer records password material: with debug logging on, BinkP authentication logged the first characters of the received and expected passwords and the CRAM-MD5 challenge with its digest. Only outcomes, lengths and a hint about why a password did not match are logged now, unless you set BINKP_LOG_SENSITIVE_AUTH=true.
Auto Feed verifies TLS and allows only http(s) feeds: RSS and Atom feeds are now fetched with HTTPS certificate and host name verification, and only http:// and https:// URLs are accepted. A feed with an invalid or self-signed certificate now fails instead of being fetched.
Telnet TLS offers TLS 1.3 and validates your certificate: the TLS Telnet listener now accepts TLS 1.3 as well as 1.0 to 1.2. TELNET_TLS_MIN_VERSION sets the oldest version accepted and TELNET_TLS_CIPHERS sets the cipher list. A certificate and key you supply are checked at start-up, and TLS stays off, with the reason logged, if they are unusable. The generated self-signed certificate is now ECDSA so that SyncTERM can connect; TELNET_TLS_KEY_TYPE=rsa restores RSA.
Login takes the same time for unknown usernames: a login with a username that does not exist used to fail faster than a login with a real username and a wrong password, which let anyone tell which accounts exist by timing the responses. Both failures now take about the same time.
WebDoor API only serves installed, enabled games: the session, storage and leaderboard endpoints used under /api/webdoor/ now accept only a game_id that names an installed WebDoor which is enabled and whose requirements are met. Any other id gets a 404 and nothing is read or written. Before, any string was accepted and created session, save and leaderboard rows.
Telnet drops idle connections sooner before login: a Telnet connection that sits silent at the login, register or reset-password prompts is now disconnected after 90 seconds instead of the 7 minutes allowed for a signed-in session. Set TELNET_PREAUTH_IDLE_TIMEOUT in .env to change it.