open
https://gitlab.synchro.net/main/sbbs/-/work_items/1307
`matchuser()` returns 0 both when the name is not in `data/user/name.dat` and when it could not open or read that file at all; `finduserstr()` likewise returns 0 when it cannot open `user.tab`. `find_login_id()` passes either through. Every authenticating server then takes the "no such user" branch for a lookup that never ran:
- logs `!UNKNOWN USER: '<name>'`, including the password when Echo Password is enabled;
- calls `badlogin()`, which counts a failed attempt against the client IP, publishes it over MQTT, writes `hack.log` at the hack threshold and, at the filter threshold, adds the IP to `ip.can` for the configured duration.
Observed on vert on 2026-10-27 while the process had exhausted its C runtime stdio streams (EMFILE, a separate leak). For about four hours every real login via POP3, SMTP AUTH, FTP, web and the terminal server was reported as an unknown user, several with passwords echoed into the mail log, and a residential IP whose mail client kept retrying with a valid account was added to `ip.can` for 90 days ("3 FAILED LOGIN ATTEMPTS in 9.6h"). Any other I/O failure on `name.dat` or `user.tab` (share outage, permissions, disk full, a concurrent writer holding a mandatory lock on Windows) produces the same misreport and the same bans.
Affected lookup sites: `mailsrvr.cpp` (POP3 USER/PASS and APOP, SMTP AUTH PLAIN/LOGIN and CRAM-MD5), `ftpsrvr.cpp` (PASS), `websrvr.cpp` (HTTP auth and `login()`), `services.cpp` (`login()`), `login.cpp`/`answer.cpp` (terminal and RLogin), and the JS wrappers `system.matchuser()` / `system.find_login_id()`.
Proposed fix: have `matchuser()` and `finduserstr()` return a negative value when the index or `user.tab` cannot be opened or read, propagated by `find_login_id()`. Authenticating callers treat a negative result as a system error: log it at LOG_ERR with the errno, answer with a temporary failure (`-ERR [SYS/TEMP]` for POP3, `454 4.7.0` for SMTP, `421` for FTP) and skip `badlogin()`, so nothing is counted, echoed or banned. The JS wrappers clamp a negative result to 0 to keep their existing contract; callers that assign straight into an unsigned `user.number` check the sign first.
— *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.38-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)