• src/sbbs3/main.cpp

    From Rob Swindell (on Windows 11)@1:103/705 to Git commit to main/sbbs/master on Saturday, May 02, 2026 18:58:15
    https://gitlab.synchro.net/main/sbbs/-/commit/ff1aae498b3464b1f2fd050a
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    sbbs3 terminal server: drop unsent goodbye output before close_socket

    In the four listener exit paths that queue output via the listener
    pseudo-sbbs (CLIENT BLOCKED for ip.can / host.can, "no nodes
    available", node init failure), call sbbs->rioctl(IOFB) right after flush_output() and before close_socket(). Without this, any
    text/badip.msg / badhost.msg / nonodes.txt residue that didn't make
    it out within the flush timeout sits in the ring buffer; when the
    output thread next wakes it tries to send the leftover on the
    now-closed FD, and the failed send logs a noisy warning (e.g.
    "!ERROR 22 (...)" or "!ERROR 58 (...)" sending on socket).

    This is the same idiom as the existing rioctl(IOFB) at the start of
    the per-connection setup (main.cpp:5795) — purge stale buffer state
    before changing socket lifecycle.

    It does not eliminate the race entirely: data already pulled from
    the ring buffer into the output thread's linear buffer is still sent
    (or attempted), since rioctl operates on the ring buffer only. The
    remaining noise is handled separately by demoting the typical
    post-shutdown send errors (ESHUTDOWN, EINVAL) to LOG_NOTICE.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Tuesday, May 05, 2026 15:55:25
    https://gitlab.synchro.net/main/sbbs/-/commit/88889e94e52406ca3ff0b721
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    node_thread: avoid throttle-loop hang on loginAttempts() failure (CID 645970)

    loginAttempts() returns long and is documented to return a negative
    value on failure, but the result was stored in a uint. On -1 the value
    became UINT_MAX, passed the (> 1) check, and the throttle loop would
    run ~4 billion mswait() iterations. Match the signed return type and
    update the matching format specifier and loop counter.
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Wednesday, May 06, 2026 19:41:53
    https://gitlab.synchro.net/main/sbbs/-/commit/ded9a017f3b2199433125aad
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    sbbs_t::lputs: guard event-thread log-level check against null startup (CID 543171)

    sbbs_t::lputs() consults startup->event_log_level when is_event_thread
    is set, but the surrounding callers (e.g. sbbs_t::js_create_user_objects) already treat startup as potentially-null. If any caller reaches an errprintf/lprintf path with startup == nullptr while is_event_thread is
    true, the deref would crash. Add the null check that the call sites
    already assume.

    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Wednesday, May 06, 2026 22:36:57
    https://gitlab.synchro.net/main/sbbs/-/commit/d6d35429bd173a0284d8fc5a
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    main: cast cryptSetAttribute SSH_CHANNEL_ACTIVE deactivation to void (CID 487166)

    In crypt_pop_channel_data the inner cryptSetAttribute that flips the
    selected channel inactive is best-effort (we're tearing down anyway).
    Make the discarded return explicit.

    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Windows 11)@1:103/705 to Git commit to main/sbbs/master on Sunday, May 17, 2026 19:37:01
    https://gitlab.synchro.net/main/sbbs/-/commit/6fb53e92e2ffbe510d6c44a1
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    sbbs: reuse ip_can / ip_silent_can .fname instead of rebuilding the path

    The trashCan instances are already initialized at startup with the full
    path in .fname; just pick the one matching filter_silent. Matches the
    existing log at main.cpp:5850 which already uses ip_can.fname directly.

    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Monday, July 06, 2026 00:00:16
    https://gitlab.synchro.net/main/sbbs/-/commit/39112546f2b835451e37289b
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Fix client-socket errors misreported as spy-socket errors on *nix (#1184)

    input_thread() identified which socket a failed/completed recv() belonged
    to by re-comparing 'sock' against the atomic sbbs->client_socket after the fact. Since sbbs_t::hangup() (and other cross-thread teardown) closes the client socket and stores INVALID_SOCKET to wake this thread, the identity re-check could fail for a socket that WAS the client socket, misrouting an ordinary disconnect into the *nix else-branch that assumes any non-client socket is the node's local spy socket. Result: routine (usually SSH)
    session teardowns were logged to error.log as, e.g.:

    Node 11 !ERROR 9 (Bad file descriptor) on local spy socket 179 receive

    where fd 179 was actually the node's passthru/client socket, never a spy connection -- sending the sysop hunting for spy-socket problems that don't exist. Observed four times since Jan-2025 on Vertrauen's Linux host,
    always coinciding with 'disconnecting client' teardown.

    Remember which socket was selected at poll time (new 'spy_sock' bool) and branch on that instead, at all four re-comparison sites: the SSH receive
    path, the receive-error handler, the EOF/'disconnected' check, and the skip-telnet-interpretation check. A post-hangup wake-up now routes to the client branch, which exits quietly via the existing !online check (hangup clears 'online' before closing the socket).

    Validated against a live scratch terminal server: client disconnect, spy connect/mirror/disconnect (clean 'Closing local spy socket' notice), and server-terminated-mid-session teardown -- no misattributed spy-socket
    errors logged. Closes issue #1184.

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Monday, July 06, 2026 00:00:17
    https://gitlab.synchro.net/main/sbbs/-/commit/8101584ded3352342328fcb6
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Terminal server: shutdown(), don't close(), node sockets at terminate/recycle

    At server termination/recycle, the terminal server thread force-
    disconnected active nodes by calling close_socket() on each node's client socket. But that descriptor is owned by the node thread: sbbs_t::hangup() closes it again during the node's own teardown (after its 1-second output- flush mswait), producing a double-close. On *nix that logged the (benign
    but alarming) shutdown-time noise:

    !ERROR 9 closing socket 11

    one second after 'Closing node N socket 11'; on Windows the same double-
    close was silent only because close_socket() exempts ENOTSOCK. Worse than
    the noise: between the two closes the fd number could be reused (a new
    inbound connection during recycle, an opened file) and hangup() would then close an unrelated descriptor out from under its new owner.

    Shut the socket down (SHUT_RDWR) instead of closing it: that wakes every blocked reader -- and unlike close(), propagates EOF through dup'd
    descriptors held by external programs -- while the fd number stays
    allocated until the owning node thread performs the one real close in
    hangup(). node_socket[] is still marked INVALID_SOCKET here, preserving
    the input_thread 'shutdown locally' signal that terminates JS execution promptly.

    Same fd-lifetime family as 39112546f2 (tables-20-ideas) / issue #1184. Validated against a live scratch terminal server: SIGTERM with an active
    client session now yields a clean 'Shutting down node 1 socket 11' ->
    'Node 1 disconnected' -> node-thread-terminated sequence with no EBADF
    errors logged.

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Deucе@1:103/705 to Git commit to main/sbbs/master on Thursday, July 16, 2026 14:26:08
    https://gitlab.synchro.net/main/sbbs/-/commit/2154c2b9295e07c6cb38f551
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    sbbs: drain buffered SSH plaintext before polling

    Cryptlib can consume all pending encrypted socket input while
    returning only one SSH channel packet from cryptPopData(). The input
    thread then waited for the raw socket to become readable before
    calling it again, leaving plaintext buffered inside Cryptlib. Large unidirectional transfers could stop near EOF until reverse traffic
    woke the socket path, causing ZMODEM timeouts and retransmission.

    After each successful SSH pop, bypass the raw-socket poll and keep
    popping through the existing input path until Cryptlib returns zero
    bytes or an error. Normal polling resumes once its buffered plaintext
    has been drained.

    Co-Authored-By: OpenAI Codex <noreply@openai.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Sunday, July 19, 2026 15:25:25
    https://gitlab.synchro.net/main/sbbs/-/commit/7853fb5c959aa15a946c9434
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Terminal server: flag nodes to re-read config on recycle signal

    When a recycle was signaled (ctrl/recycle semaphore, MQTT recycle topic, SIGHUP/systemctl reload, or a console recycle command), the terminal
    server only acted on it once every node was idle: the recycle detection
    was gated on node_threads_running == 0. Until then, new connections
    kept attaching with the old configuration, so on a busy board that never
    fully drains, hand-edited config changes could effectively never reach
    new logins.

    Detect the recycle request every iteration now, and when one is seen,
    set the NODE_RRUN flag on this instance's nodes (first_node..last_node)
    so each node re-reads its per-node configuration on its next connection
    -- the same mechanism SCFG's refresh_cfg() already uses. The full
    server recycle (which re-reads the master config and reinitializes the listening sockets) still waits until all nodes are idle, so this only
    adds immediate per-connection config propagation without changing when
    the server-level reinitialization happens.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Tuesday, July 21, 2026 21:53:27
    https://gitlab.synchro.net/main/sbbs/-/commit/473e880ec144eb322ad4b5ec
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Terminal server: transmit the final output before hanging up

    sbbs_t::hangup() gave the output ring buffer a blind mswait(1000) and then
    tore the connection down, never confirming that the pending output had
    actually been sent. Over SSH the last output was lost every time -- most visibly the echoed command key of a fast log-off (/O), which produces no
    other output at all, so the user saw their keystroke simply vanish.

    Wait for the output to be transmitted instead, using WaitForOutbufDrained() (added in 9cf170c024, squad-4-memo, for issue #1157). WaitForOutbufEmpty() alone would not do: outbuf.empty_event fires when output_thread moves the
    ring buffer into its linear buffer, before the send, and the ssh_session_destroy() a few lines below then discards those still- untransmitted bytes. On telnet the equivalent window ends in sendsocket(), whose kernel buffer survives until close() -- hence the SSH-only symptom.

    The wait is performed while still online, so that flush_output()'s online short-circuit would not apply and input_thread keeps draining the receive
    queue meanwhile. It also turns the unconditional one-second sleep into a one-second ceiling, so an ordinary hang-up now completes as soon as the
    output has drained.

    Verified with three different SSH clients.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Tuesday, July 21, 2026 23:33:58
    https://gitlab.synchro.net/main/sbbs/-/commit/454cc802610dcfa661db6178
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Terminal server: apply the 8KB SSH send clamp that was never wired up

    757e389522 (deputy-13-floating) limited cryptPushData() sends to 8KB in
    both the web and terminal servers, after the same limit fixed a problem
    in js_socket.c. The web server half took effect; the terminal server
    half never did. output_thread() computed sendbytes and clamped it, then
    called cryptPushData() with buftop - bufbot anyway, so the variable has
    been dead ever since. Because the clamp expression reads sendbytes, no compiler warns about it.

    Pass sendbytes to the push, as websrvr.cpp and sftp.cpp both already do.
    Where getsockopt(TCP_MAXSEG) succeeds this changes nothing -- mss then
    caps a linear-buffer read well below 8KB -- but where TCP_MAXSEG is
    unavailable mss stays IO_THREAD_BUF_SIZE (20000) and pushes of up to
    ~20KB were bypassing the intended limit.

    Hoist sendbytes to cover the whole send, so that it means "bytes offered
    to the transport this iteration" for the sendsocket() path as well, and
    key the short-send warning off it. Otherwise a clamped push would be
    reported as a short send every time. The error paths that discard the
    linear buffer ("pretend we sent it all") reset sendbytes to the full
    remainder first, preserving their existing behavior of dropping it in
    one go rather than in 8KB steps.

    No functional change on this host, where TCP_MAXSEG is available.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Thursday, July 23, 2026 23:03:46
    https://gitlab.synchro.net/main/sbbs/-/commit/bd9a08274766f7097074dad9
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    main: suppress output_thread INTEGER_OVERFLOW false positive (CID 651667)

    Coverity flagged the linear-buffer sends in output_thread() as passing a possibly-underflowed size to sendsocket(): sendbytes (buftop - bufbot) to
    the client-socket send, and i to the spy-socket send. Neither can wrap.
    The refill guard at the loop top keeps 0 <= bufbot < buftop wherever the subtraction runs, so sendbytes >= 1; and every error path resets i to a non-negative sendbytes before bufbot += i, so i is never negative where it feeds the spy send. The checker simply can't prove bufbot <= buftop across iterations.

    Annotate both sites with coverity[INTEGER_OVERFLOW:SUPPRESS] and the
    one-line invariant, matching the existing precedent in atcodes.cpp. No behavioral change.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Friday, August 14, 2026 19:14:44
    https://gitlab.synchro.net/main/sbbs/-/commit/903d316dda30b9278dbbf943
    Modified Files:
    src/sbbs3/main.cpp
    Log Message:
    Terminal server: don't report a session teardown as an SSH error

    hangup() destroys the cryptlib session while input_thread sits between
    its ssh_mode test and its crypt_pop_channel_data() call, so the pop can
    fail on a handle that is already gone. The status is normally CRYPT_ERROR_PARAM1, which GCES logs at LOG_ERR:

    Node 11 SSH ERROR 'Bad argument, parameter 1' (-1) popping data

    Every clean SSH disconnect therefore produced a line that reads like a
    failure. Five days of terminal-server logs on Vertrauen held 355 of
    them, each immediately preceded by a teardown message (disconnecting
    client, passthru socket disconnected, passthru or output thread
    terminated) and none on a live session. The noise imitates the genuine mid-session drops tracked in #1225, which makes those logs harder to
    read.

    Detect the already-terminating session and log it at LOG_DEBUG instead.
    The node was being torn down either way, so only the severity changes;
    an error arriving while the session is still up is still reported
    through GCES.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)