• src/sbbs3/websrvr.cpp

    From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Fri Dec 12 23:42:12 2025
    https://gitlab.synchro.net/main/sbbs/-/commit/b644e8ea3be1fca6d9f36346
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Resolve GCC warning

    warning: comparison of integer expressions of different signedness: ‘ssize_t’ {aka ‘long int’} and ‘size_t’

    ---
    ï¿­ 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 Dec 13 15:27:56 2025
    https://gitlab.synchro.net/main/sbbs/-/commit/637e4a3d2e0f8367bfdc641c
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Hack to avoid GCC warning (passing string constant, e.g. "", isn't allowed)

    warning: argument 2 null where non-null expected [-Wnonnull]

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deucе@VERT to Git commit to main/sbbs/master on Sun Dec 21 01:25:31 2025
    https://gitlab.synchro.net/main/sbbs/-/commit/d7473b5234deb90d10d3e42c
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Fix warning about too many parenthesis.

    I guess I can't just toss them in the code at random to avoid warnings
    anymore. :(

    ---
    ï¿­ 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 Sat Dec 27 01:17:57 2025
    https://gitlab.synchro.net/main/sbbs/-/commit/c50e5e9536d6796b3d57e18a
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Fix off-by-one bug in size argument passed to strlcpy()

    As noted in strlcpy man page: "a byte for the NUL should be included in size."

    ---
    þ 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 Thu Feb 5 16:10:03 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/4281523d6b7b0d41514ac7ff
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    If VHOSTS and ONE_HTTP_LOG options are enabled, log VHOST instead of HOST value

    As suggested in the Virtual Hosts section of https://httpd.apache.org/docs/2.4/logs.html, replacing the first argument
    with the vhost value makes the (single/combined) log file output more useful with virtual hosts.

    This is to address issue #1062

    ---
    þ 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 Thu Feb 5 20:24:44 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f3cae23fec4fe6dee108db25
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Fix likely harmless typo in previous commit

    caught via GCC warning: operation on ‘host’ may be undefined

    ---
    ï¿­ 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 Fri Feb 6 21:35:09 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/52e1ee602a8a3f6439e168e3
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Enable periodic cleanup and logging of the rate-limiting status

    ... using debug-level log messages.

    Ideally these details would be reported via MQTT (instead or in addition)

    ---
    þ 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 Sun Feb 8 22:34:34 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/5778df870947ebf898c48d13
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Revert "If VHOSTS and ONE_HTTP_LOG options are enabled, log VHOST instead of HOST value"

    This reverts commit 4281523d6b7b0d41514ac7ff52a999292c9d2d26.

    ---
    þ 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 Thu Feb 12 01:16:47 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/511ea86a92674be9e91acbc2
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    The Refer [sic] and User-agent custom log format directives need NULL protect

    These pointers can be null or blank, so do the CLF '-' thing

    ---
    þ 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 Thu Feb 12 01:22:23 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/d7a5b7e1dd969e9972e1a41e
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    The Custom Log Format directives are case-sensitive (%h != %H)

    ---
    þ 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 Mon Feb 16 21:09:27 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/b210bfee35785deaeb57abdb
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Add %p (server port) to custom log format supported directives

    <nelgin> DigitalMan would you mind implementing %p for server port please.
    The apache vhost combined logfile uses it.

    ---
    þ 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 Feb 24 16:28:36 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/fd0b9d2bc21b13fa9840e190
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Remove (long-deprecated) access.ars file support (use webctrl.ini instead)

    Fix issue #1083

    ---
    þ 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 Sun Mar 8 05:28:49 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/0c0cb7c473285ab8c71f209a
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Allocate extra byte for NUL-terminator in read_post_data()

    This looks like an off-by-one bug in this one call to realloc(), not adding one for the NUL terminator in this case.

    Potential cause of issue #1094

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deucе@VERT to Git commit to main/sbbs/master on Sun Mar 8 20:47:44 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/07ed41c51fd336b9d7c5d9b4
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Add NUL to end of post data file

    ---
    ï¿­ 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 Wed May 6 19:41:53 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/3ad3f0282bcff37fa1926121
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: cast away two best-effort unchecked returns (CIDs 639932, 639941)

    CID 639932: remove(cleanup_file[i]) in close_request — best-effort
    cleanup of temporary request files; failure is benign.
    CID 639941: setsockopt(TCP_NODELAY) in http_session_thread — latency
    hint; failure is non-fatal. Also widen the bool nodelay to
    int so it has correct setsockopt() type.

    Co-Authored-By: Claude Opus 4.7 <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 Wed May 6 19:41:53 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/62b41cd33f2fa4292d83710d
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: handle getuserdat failures in http_logon and check_ars (CIDs 516407, 516410, 639949)

    Both call sites set user.number then read the rest of the user record
    via getuserdat(). On read failure the user struct was left partially
    populated, then used for password comparison or downstream session
    state. Treat the failure as a system error: log it and either fall
    back to an unauthenticated session (http_logon) or reject the auth
    attempt (check_ars).

    Co-Authored-By: Claude Opus 4.7 <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 Wed May 6 19:41:53 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/65d46495860f18fb2c9a5882
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: suppress send_error ORDER_REVERSAL false-positive (CID 631137)

    Coverity reports an ORDER_REVERSAL between link_list.mutex and
    jsrt_mutex when http_session_thread calls send_error() in the
    client-limit branches. The link_list helpers in this thread
    (loginAttempts, client_on, listCountMatches) acquire+release their
    list mutex internally — nothing holds a list mutex when send_error
    runs js_setup() which acquires jsrt_mutex. Annotate both 503/429
    send_error sites with a SUPPRESS plus rationale.

    Co-Authored-By: Claude Opus 4.7 <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 Wed May 6 22:36:57 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/6ad832522da440e614b8fcdf
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: clamp tls_sent and explicit cast in sess_sendbuf return (CID 639935)

    The TLS path assigns 'result = tls_sent' where tls_sent is int and
    could theoretically be negative on cryptlib edge cases. Adding it
    to size_t 'sent' would underflow. Guard with 'if (result > 0)'.

    Also make the size_t-to-int returns explicit casts so Coverity sees
    the narrowing is intentional.

    Co-Authored-By: Claude Opus 4.7 <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 Wed May 6 22:51:40 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/c7df44f17c494f7277ac112e
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: skip getuserdat for anonymous sessions in http_logon

    Regression from 9e7649fe0: when http_logon is called with usr=NULL
    on an anonymous request (session->user.number == 0), getuserdat
    legitimately fails because user 0 doesn't exist, which now spams
    the log with '!ERROR reading user #0 data' on every anon hit.

    Only call getuserdat when there's an actual user number to read.

    Co-Authored-By: Claude Opus 4.7 <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 Wed May 6 23:04:20 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/c94f75aa58112c228a8cdce9
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: include protocol, IP, request, and ARS in no-auth log

    The "!No authentication information" debug log line now reports the
    protocol, client address, request line, and the ARS string that triggered
    the auth requirement, so it's actionable when WEB_OPT_DEBUG_RX is on.

    Co-Authored-By: Claude Opus 4.7 (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 May 9 14:04:17 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/f7b10a614935817ba8965ec1
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: don't call destroy_session() with sentinel tls_sess value (-1)

    When TLS setup fails after add_private_key() returns an error, the code
    calls cryptDestroySession() directly and sets tls_sess = -1, then calls close_session_no_rb() which would pass -1 to destroy_session(), triggering
    a spurious "Destroying a session (-1) that's not in sess_list" error.

    Co-Authored-By: Claude Sonnet 4.6 <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 Thu Jun 4 09:44:21 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/50258e70bf63ba7a82af7515
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: detect TLS client disconnect in session_check() (#1155)

    session_check()'s is_tls branch treated a readable socket as "connected"
    and latched session->tls_pending; once set, it returned "connected" on
    every later call without re-probing the socket. But a peer's TLS
    close_notify (and a FIN) arrive as readable bytes, so after an HTTPS
    client hung up, session_check() reported it connected forever. The
    JavaScript disconnect check in js_OperationCallback (ead5ccf16) relies on session_check(), so its abort never armed (offline_counter stayed 0): a badly-formed SSJS/XJS page that loops on mswait() without checking for disconnection (e.g. the webv4 user/system stats) ran forever, pinning its http_session thread, a MaxClients slot, and a CLOSE_WAIT socket -- a pile
    of zombie HTTPS clients in sbbsctrl/MQTT and eventual MaxClients
    exhaustion.

    Why this only bit Windows: socket_check() (xpdev) has two paths. On
    non-Windows builds it uses poll() (CFLAGS += -DPREFER_POLL, set only in build/Common.gmake, i.e. the GNU-make/Unix builds). poll() reports
    POLLHUP when the peer closes its end -- even while there is still buffered
    data to read -- and socket_check() returns false on POLLHUP before it
    ever runs the readable/MSG_PEEK logic. So on Unix the close was detected, session_check() returned false, and tls_pending never latched. Windows (MSBuild) does not define PREFER_POLL and uses select(), which has no
    POLLHUP equivalent: a closing TLS socket simply looks "readable"
    (MSG_PEEK returns the encrypted close_notify bytes), so the latch was set
    and the disconnect masked. The session_check() bug is platform-
    independent; poll()/POLLHUP merely hid it everywhere except Windows.

    Fix: drop the tls_pending liveness latch. Use peeked_valid (a decrypted
    byte already buffered) as the readable fast-path, and when the raw socket
    is readable, probe via cryptPopData(1 byte) -- which a raw MSG_PEEK
    cannot do -- to tell apart application data (connected; the byte is
    cached in session->peeked so the next sess_recv() returns it), CRYPT_ERROR_TIMEOUT (connected, no app data yet) and CRYPT_ERROR_COMPLETE
    (peer closed -> disconnected). The probe is non-blocking (CRYPT_OPTION_NET_READTIMEOUT == 0, set at session setup) and runs in the session's own thread, so there is no concurrent reader. Also close the
    socket in place in recvbufsocket() when session_check() reports a
    disconnect (it previously relied on the latch returning true and the
    following sess_recv() failing).

    Latch introduced in d93478b918 (famous-15-sons); the readable-as-
    connected + tls_pending set predates it (dbbfabf1b1, funky-27-foam).

    Validated on a production Windows server: CLOSE_WAIT count ~22 -> 0,
    sbbsctrl thread count 221 -> 25, and ran overnight with no zombie HTTPS clients.

    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 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
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Wed Sep 30 20:48:07 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/651cbc9886250cb97169edf8
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Web Server: shutdown() lingering session sockets when the client wait expires

    At terminate/recycle the web server thread waits up to MaxInactivity for the active clients to disconnect on their own, then gives up with "!TIMEOUT
    waiting for N active clients" and enters cleanup(), which loops until every child thread has exited. Nothing between those two steps did anything to
    the sessions that were still alive: a session thread blocked in a socket
    wait (the request-line read, the TLS close dance, the output thread's send) stayed blocked, cleanup() has no deadline, and the process never exited.
    On 2026-09-30 one idle HTTPS session on this host held the whole httpd
    service in "Waiting for 2 child threads to terminate" until systemd's 90 s
    stop timeout SIGKILLed it, 60 s of that budget having already gone to the active-client wait.

    Once the active-client wait expires, shutdown(SHUT_RDWR) the socket of
    every session still registered in current_connections. That wakes the
    blocked reader (recv() returns 0, poll() reports HUP) and lets the session thread run its normal teardown and exit; cleanup() then finishes within a second or two. Shutting down rather than closing keeps the descriptor
    owned by the session thread, which still performs the one real close, the
    same way the terminal server handles node sockets at terminate (8101584ded, symbol-19-seek, 2026-07-05).

    To make that safe, the session's current_connections node is now removed
    in close_session_socket(), immediately before the descriptor is closed,
    instead of at session-thread exit. Both the removal and the shutdown pass
    take the list lock, so the list never names a closed (and possibly reused) descriptor and a socket found in it is guaranteed to still be open.

    Validated against a scratch web server (MaxInactivity = 10s) with a plain
    HTTP client that sends a partial request line and then one byte every two seconds, so the session's inactivity timer never fires: before this change
    the server was still "Waiting for 2 child threads" 60 s after SIGTERM and
    had to be SIGKILLed; after it, the session is shut down the same second the client wait expires and the process exits 13 s after SIGTERM.

    Not covered: a connection still inside the TLS handshake is not yet in current_connections (it is added after the handshake, in client_on), so it cannot be kicked; cryptlib bounds that handshake at about 30 s. Which call
    the 2026-09-30 session thread was actually blocked in is not known (the
    SIGKILL left no core), so this is the safety net for that class of hang,
    not a fix for a specific blocking call.

    Co-Authored-By: Claude Fable 5.1 <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 Wed Sep 30 20:48:07 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/a45592ec5bae024adf511679
    Modified Files:
    src/sbbs3/websrvr.cpp
    Log Message:
    Web Server: bound the shutdown wait for child threads, re-kick while waiting

    cleanup() looped on thread_count with no deadline. The previous commit
    (the shutdown() kick of listed session sockets) ends that loop quickly
    whenever the lingering thread is blocked on its client socket, but a thread blocked anywhere else - a script in a native call, a CGI pipe, a mutex -
    is out of its reach, and one such thread still held the whole server (and
    under systemd, the stop timeout) hostage until SIGKILL.

    Give up after TIMEOUT_THREAD_WAIT (60 s), the same bound the terminal, mail
    and FTP servers put on their own thread waits, logging "!TIMEOUT waiting
    for N child thread(s) to terminate" at error level. While waiting, re-run shutdown_sessions() every 5 s: a connection that completes its TLS
    handshake after the first pass is only then registered in
    current_connections, and each pass also logs what is still connected. A
    thread still running at the deadline is abandoned; the counter is left undestroyed (with a "!!!! Terminating with N child thread(s) still running" warning, as active_clients already does) so its eventual thread_down() is harmless. thread_count is initialized once per server start, not per
    recycle, so a thread abandoned by a recycle still decrements the right
    counter when it finally exits.

    Validated against a scratch web server (MaxInactivity = 10s) with an SSJS
    page that calls mswait(120000): before, the kick fired and the server was
    still "Waiting for 2 child threads" when SIGKILLed at 100 s; after, it logs
    the timeout and exits cleanly 73 s after SIGTERM (10 s client wait + 60 s thread wait), exit code 0, no core. The slow-client scenario from the
    previous commit still exits in 13 s.

    Budget note for systemd hosts: the worst case is now MaxInactivity plus
    60 s. With the default TimeoutStopSec of 90 s and MaxInactivity = 1m,
    SIGKILL still arrives first; raise TimeoutStopSec or lower MaxInactivity
    if the timeout diagnostics are wanted in that case.

    Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

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