• src/sbbs3/websrvr.cpp

    From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Sun Jun 21 20:54:25 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/65643e6ca604c3520da18e50
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: bound drain_outbuf() so a dead client can't wedge the server

    drain_outbuf() spun in a SLEEP(1) loop as long as the outbuf ring buffer
    held data and the socket was still valid, with no timeout and no check of
    the terminate_server flag (the "/* ToDo: This should probably timeout eventually... */" note acknowledged this). When a client stops reading,
    the output thread blocks in its send and the buffer never drains, so the session thread spins forever. Under a distributed web scrape (many
    abandoned Alibaba/Aliyun keep-alive connections) this hung web-server
    shutdown: the "Waiting for N child threads to terminate" loop never
    completed because several http_session_thread()s were stuck in
    drain_outbuf() <- send_error().

    Bound the wait: return (not break) when terminate_server is set, or once
    the buffer has stalled for max_inactivity seconds. Returning rather than falling through matters - the output thread can hold outbuf_write while
    blocked in a send, so the trailing pthread_mutex_lock() would just re-hang; returning lets the caller close the socket, which unblocks the output
    thread.

    Unbounded since the original SLEEP-based drain in 00f254912d (maker-8-money).

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Tue Jun 23 13:40:10 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f6d382c13949040c841d4465
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: add debug-level timing probes to localize webv4 login stall (#1169)

    Issue #1169 reports an exactly-90-second stall on every webv4 portal login/logout, logged between "Initializing User Objects" and the first
    "Adding query value" line. It was initially suspected to be related to
    #1153 (Windows exclusive user.tab locking), but the reporter confirmed
    the stall persists on a current nightly that already carries the #1153
    fix, so it is unrelated.

    Tracing the path shows js_CreateUserObjects() and its area-object
    creators only build lazy JS skeletons and take no user.tab lock, and the stalling request is anonymous (no user-record write at all), so the
    native "Initializing User Objects" step is an unlikely culprit. To
    localize the delay empirically, add LOG_DEBUG probes that bisect the gap between that log line and query-string parsing:

    - http_checkuser(): "User Objects initialized" (bounds js_CreateUserObjects)
    - check_request(): "Authorization check complete" (bounds check_ars tail)
    - respond(): "Responding to request (dynamic=%d)"
    - exec_ssjs(): "beginning JS request" / "initializing request properties"
    (brackets JS_BEGINREQUEST to catch a blocking begin-request)

    The adjacent pair of lines that straddles the 90s gap in a debug log
    localizes the offending region. Probes are tagged "#1169 timing probe"
    for easy removal once root-caused.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Tue Jun 23 22:21:39 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/b0f02c4e61aa835f2b9b9e21
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: read buffered TLS request body directly (fix #1169 login stall)

    A webv4 login/logout is an HTTPS POST whose body (credentials) often arrives
    in the same TLS record as the headers, so it sits decrypted-but-unread in the TLS layer with nothing left on the raw socket. read_post_data() -> recvbufsocket() gated each read on session_check(), which since 50258e70b ("detect TLS client disconnect", #1155) only treats a TLS session as readable when a byte has been peeked (peeked_valid) - it no longer short-circuits on tls_pending. With the body buffered but no peeked byte, session_check() fell through to socket_check() on the raw socket and blocked for the full MaxInactivity timeout (60-90s) before the buffered body was finally read.
    That is the #1169 "login stalls ~90s at Initializing User Objects" symptom: POST-only (login/logout), duration == MaxInactivity, no wire traffic.

    Guard the recvbufsocket() wait with tls_pending the same way sockreadline() already does for header reads: when TLS data is already buffered, read it directly instead of waiting on the raw socket. Header reads were unaffected because sockreadline() kept its own tls_pending guard; only the body read regressed.

    Manifests whenever the body is TLS-buffered at read time (reliably on Windows, intermittently on Linux v3.22a); absent in v3.21f, which predates 50258e70b. Verified on vert: the auth POST's "Authorization check complete" -> "Responding to request" gap went from 60s to 0s.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Tue Jun 23 23:20:54 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/659de100d04037459107de30
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: remove #1169 timing probes (issue resolved)

    Reverts the debug-level timing probes added in f6d382c13 to localize the
    webv4 login stall; #1169 is now root-caused and fixed in b0f02c4e6 (recvbufsocket reads buffered TLS data directly instead of waiting on the
    raw socket for MaxInactivity).

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Tue Jun 23 23:20:54 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/a6cb9dffb18f6ba070113911
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: consolidate js_CreateUserObjects() branches in http_checkuser()

    The user>0 and guest (NULL user) branches differed only in the user argument and an error-log string; collapse them into a single call with a ternary for the user pointer. No functional change (the anonymous failure path now logs the same "creating user objects" message as the authenticated path).

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Tue Jun 23 23:20:54 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/3e57627a712015d1e417056b
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: log authenticated user logon/logoff at LOG_INFO

    http_logon()/http_logoff() logged every web logon and logoff at LOG_DEBUG, so webv4 (and HTTP-auth) user logins were invisible in the server log unless debug-level web logging was enabled - unlike the FTP (ftpsrvr.cpp:2695), mail (mailsrvr.cpp:1422/4380/4489) and terminal (answer.cpp:452) servers, which all record a successful user login at LOG_INFO.

    Log a logon at LOG_INFO when a real user authenticated (user.number > 0) and keep anonymous/Guest logons (number == 0) at LOG_DEBUG, so the constant per-request anonymous churn (bots, crawlers) stays quiet. http_logoff() already early-returns unless a user was logged in, so it moves to LOG_INFO unconditionally.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Tue Aug 4 22:12:21 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/0ca5676148730160b440cf02
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: always read the file header when resolving a file-vpath request

    4de1032086 (payroll-20-queue, 2026-08-01) skipped the header record for
    a file in a free directory, on the grounds that loadfile() zeroes the
    cost there and download_is_free() short-circuits on the same flag before
    any credit comparison.

    The cost is not the only field that comes from the header. The index
    record stores the filename truncated to SMB_FILEIDX_NAMELEN (64 bytes, extension preserved), and smb_getfile() points file.name at it, so at
    index detail file.name was that truncated form. Names longer than the
    limit are not hypothetical: one in this file base is 78 characters. The
    name reaches user_downloaded_file(), which embeds it in the uploader's
    "file downloaded" notification, and mqtt_file_download(), which
    publishes it.

    The transfer and the credit accounting survived it, because deriving an
    index name from an already-truncated one is idempotent and the record
    still resolved, but the name recorded and announced was wrong.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Sat Aug 8 18:05:04 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/0791f3e3bfcdb04afeda7134
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Don't hand a CGI the web server's descriptors (#1174)

    The CGI child sets up stdin, stdout and stderr and then execs with everything else this process had open still in hand. Close the rest, as externals now
    do. Nothing above stderr is a CGI's business: the client socket it reads
    from, where it has one, has already been duplicated onto stdin.

    Verified against a listen socket left deliberately inheritable, standing in
    for the descriptors this cannot otherwise reach - the ones opened inside libraries, where there is no call site to mark. Before, the CGI inherited it; after, the CGI starts with stdio alone.

    The exec-failure message now goes to stderr directly, which is the pipe the parent already reads and logs as a CGI error. errprintf() is not usable after the close: it is not fork-safe, and its descriptors are among those closed.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Sat Sep 19 23:53:09 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f9558d66d5a2d9e7df880e7c
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: terminate the CGI read loop on a FastCGI socket failure

    fastcgi_read_wait_timeout() reported its recv(), version and session-ID failures by returning ret, which is still 0 at each of those points. Zero
    is the function's "nothing ready yet" value, so do_cgi_stuff() skipped its whole body, including the CGI_PROCESS_TERMINATED check, and polled again immediately. A socket at EOF is always readable, so nothing paced the
    retry: a backend that closed its connection left the session spinning at roughly 1.6 million iterations per second, logging at LOG_ERR on every
    one, until max_cgi_inactivity expired as much as two minutes later.

    Return CGI_PROCESS_TERMINATED from those three paths and from the two fastcgi_read_body() failures below them, so the caller tears the session
    down at once instead of treating a dead socket as a slow one.

    A php-fpm restart is enough to trigger this, which means it fires during routine package upgrades.

    #1246

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net