• docs/v322_new.md src/sbbs

    From Rob Swindell (on Windows@VERT to Git commit to main/sbbs/m on Friday, July 17, 2026 03:05:00
    https://gitlab.synchro.net/main/sbbs/-/commit/9fc5505cbc4dfc9aeaab00ff
    Modified Files:
    docs/v322_new.md src/sbbs3/ctrl/build.bat src/sbbs3/jsexec.vcxproj ntsvcs.vcxproj sbbscon.vcxproj
    Log Message:
    Windows: build all 32-bit Synchronet hosts /LARGEADDRESSAWARE

    Mark sbbs.exe (sbbscon.vcxproj), sbbsNTsvcs.exe (ntsvcs.vcxproj),
    jsexec.exe (jsexec.vcxproj), and sbbsctrl.exe (ctrl/build.bat) as large-address-aware, raising their usable address space from 2GB to
    ~4GB on 64-bit Windows.

    This is a stopgap for GitLab #1185: an access violation in mozjs185
    seen only on 32-bit Windows under JS heap pressure (a web-scrape
    attack), never on Linux-x64. It raises the ceiling; it does not fix
    the underlying pointer corruption, which the SM128 / 64-bit migration
    addresses properly.

    The MSVC projects get <LargeAddressAware>true</LargeAddressAware> in
    both Debug|Win32 and Release|Win32 link steps. Borland's ilink32 has
    no LAA option, so build.bat flips the PE-header bit post-build
    instead.

    Validated: the 2006-era Borland host runs stably under LAA with
    system-wide top-down allocation (Synchronet DLLs executing above 2GB,
    all servers listening, no errors), and jsexec exercised the mozjs GC
    and trace-JIT with ~3.4GB live above 2GB without failure. Sustained
    behavior under real scrape load is not yet proven.

    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@VERT to Git commit to main/sbbs/m on Saturday, July 18, 2026 22:02:00
    https://gitlab.synchro.net/main/sbbs/-/commit/3a5e9cb3628031f8812840c5
    Modified Files:
    docs/v322_new.md src/sbbs3/scfg/scfg.c scfg.h scfgmsg.c
    Log Message:
    Restore SCFG import/export of subs.txt, alongside subs.ini

    subs.txt import/export was removed in mayor-19-caps (e6acb3269a) and its
    menu slots later repurposed for *subs.ini in reef-31-feed (cfb32e03f5).
    Some sysops still rely on the legacy text format (especially for import),
    so this restores it as an additional format while keeping *subs.ini as
    the preferred format going forward.

    Message Areas > Export/Import Areas list subs.txt again, the -import=
    command line recognizes a subs.txt filename once more, and the
    line-oriented parser/writer are back.

    Rather than the old memcpy-over-the-existing-sub (which zeroed every
    field the format doesn't carry - qwk_conf, print modes, area_tag, ...),
    the import now copies only the fields subs.txt actually provides, leaving
    the rest of an over-written sub intact. ptridx is likewise preserved
    (the exported value is meaningless on the importing system).

    Partially addresses issue #1128.

    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 L@VERT to Git commit to main/sbbs/m on Sunday, July 19, 2026 15:25:00
    https://gitlab.synchro.net/main/sbbs/-/commit/8a79c1ff830f811d824cced5
    Modified Files:
    docs/v322_new.md src/sbbs3/nodedefs.h
    Log Message:
    Document node-rerun-on-recycle behavior; fix stale NODE_RRUN comment

    Add a v3.22a release note for the terminal-server change that flags
    active nodes to re-read their configuration when a recycle is signaled,
    so configuration changes reach new callers without waiting for every
    node to disconnect.

    Also correct the NODE_RRUN definition comment. Its only remaining
    effect in the threaded terminal server is to make a node re-read its configuration on the next logon; nothing is "re-run" and there is no logoff-time action (unlike its neighbors NODE_EVENT and NODE_DOWN).
    The old "Re-run this node when log off" wording described the DOS/OS2 separate-process node model that no longer exists.

    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 L@VERT to Git commit to main/sbbs/m on Friday, July 24, 2026 11:16:00
    https://gitlab.synchro.net/main/sbbs/-/commit/04b47329aa0cfbc839ec1a4c
    Modified Files:
    docs/v322_new.md src/sbbs3/zmodem.c
    Log Message:
    zmodem: fix SIGFPE when the transmit window is under 4 blocks (#1197)

    zmodem_send_from() picks the ack-request interval as

    subpkts_sent % (max_window_size / block_size / 4)

    which is a division by zero whenever the window is narrower than four
    blocks: with -8 -w8192 the divisor is 8192 / 8192 / 4, that is 1 / 4,
    that is 0. sexyz then dies with SIGFPE right after the ZRQINIT/ZRINIT handshake, having put about 3 KB of handshake on the wire and no file
    data at all. Also affected: -8 -w16384, -2 -w4096, and the same window
    sizes set through MaxWindowSize in sexyz.ini.

    Clamp the interval to a minimum of one, so a window narrower than four
    blocks requests an ACK on every subpacket, which is the only sensible
    reading of "every quarter window" at that size.

    Verified against lrz over the bench harness: -8 -w16384 and -2 -w4096
    both used to die on signal 8 and now complete with a matching SHA-256, at
    3.08 and 0.71 MB/s. Windows that already worked are unchanged, -w32768
    at 4.88 MB/s and -w65536 at 5.03, as is streaming without -w at 11.6.

    SyncTERM shares zmodem.c but never sets max_window_size, so it leaves the
    value at zero, short-circuits the branch, and cannot reach this. The
    exposure is sexyz only.

    A window exactly equal to the block size (-8 -w8192) no longer crashes
    but is still pathologically slow, roughly one subpacket per second with a one-second receive timeout each iteration. That is tracked separately in
    the issue; it is not a regression, since it used to be a crash.

    zmodem_ver bumped 2.3 -> 2.4. Release notes updated, including the already-shipped 2 GB windowed fix and Deuce's send-path speedups, which
    had no v322 entry.

    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 L@VERT to Git commit to main/sbbs/m on Friday, July 24, 2026 23:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/aaf394a0d833acd89a64d3a9
    Modified Files:
    docs/v322_new.md src/sbbs3/sexyz.c
    Log Message:
    sexyz: keep the transmit window >= 4x the block size, like lsz (#1197)

    sexyz's -w set only the ZMODEM transmit window and left the block size independent (from -2/-4/-8, default 1 KB), so a window at or below the
    block size was configurable -- and it stalled the transfer to roughly one window per second. lsz and Forsberg sz never allow this: when -w is
    given they force the block down to a quarter of the window
    (blkopt = Txwspac = Txwindow/4), guaranteeing window >= 4*block so the ack-every-quarter-window scheme always has several blocks in flight.

    Do the same after argument parsing, so -8 -w8192 and -w8192 -8 both
    resolve the same way: floor the window to 256, round to a multiple of 64,
    and reduce max_block_size to window/4 when it exceeds it (and the start block_size with it). A one-line notice reports the reduction.

    Effect: the formerly-hanging window<=block configurations now complete --
    -8 -w8192 reduces the block to 2048 and transfers, -4 -w4096 to 1024,
    and so on -- while windows that already left four blocks of headroom
    (-w32768 and up with -8) are untouched and unchanged (4.75 and 4.88 MB/s
    here, streaming still 11.3). This also makes the divide-by-zero guarded
    in 04b47329aa unreachable by configuration, since window/block/4 is now
    always >= 1.

    This does not make sexyz's windowed throughput competitive with lsz --
    at the same block and window sexyz is still many times slower, and very
    small windows (which force tiny blocks) remain slow with occasional
    one-second stalls. That is the separate sexyz windowed-send overhead
    tracked in #1195; this change is only about not permitting the degenerate window<=block configuration that lsz forbids by construction.

    sexyz.c version 3.3 -> 3.4. No zmodem.c change, so SyncTERM is
    unaffected (it never sets a transmit window anyway).

    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@VERT to Git commit to main/sbbs/m on Saturday, July 25, 2026 22:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/b17b1d3e3e45bbbc4df47ae6
    Modified Files:
    docs/v322_new.md src/sbbs3/atcodes.cpp getkey.cpp js_bbs.cpp putmsg.cpp sbbs.h terminal.h
    Log Message:
    Unicode @-codes: return the expansion instead of printing it

    @U+XXXX@, @CHECKMARK@, @ELLIPSIS@, @COPY@, @SOUNDCOPY@, @REGISTERED@, @TRADEMARK@, @DEGREE_C@, @DEGREE_F@ and @WIDE:text@ wrote their glyph
    straight to the terminal from inside atcode() and returned an empty
    string. Format modifiers were therefore applied to "": @CHECKMARK-R10@
    emitted the glyph and then right-aligned nothing in a 10-column field.
    Callers that expand an @-code into a buffer for a non-printing reason -- bbs.atcode(), bbs.expand_atcodes(), format_text(), mnemonics(),
    putnode(), notify() and putmsg()'s @CENTER@ -- got an empty expansion
    plus stray output on the current node's terminal.

    These codes now encode into the caller's buffer and set P_UTF8, so show_atcode()'s existing display-width compensation makes alignment and truncation work. formatted_atcode() and expand_atcodes() take an
    optional pmode out-param so the buffer-expansion callers can learn the
    result is UTF-8; @CENTER@ and mnemonics() use it, the latter needing the
    P_UTF8 branch bputs() already has, since it emits byte-by-byte through outchar(). Terminal::center() measures with bstrlen(str, mode & P_UTF8):
    the other code sets bstrlen() recognizes are translated by putmsg(), not bputs(), and a centered line bypasses putmsg()'s character loop.

    The |W (doubled) modifier prints a UTF-8 expansion as-is rather than
    feeding it to wide(), which would double each byte rather than the glyph. Output buffers grew to fit @WIDE:, which triples byte length.

    Introduced in 9374bfe37e (pill-4-pencil), a9eea57362 (begun-3-ended) and 43c793a14b (hold-20-maps).

    Fixes #1198

    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 Windows@VERT to Git commit to main/sbbs/m on Saturday, July 25, 2026 22:24:00
    https://gitlab.synchro.net/main/sbbs/-/commit/dbc654505f7d822a8ece7c0d
    Modified Files:
    docs/v322_new.md src/sbbs3/con_out.cpp terminal.cpp
    Log Message:
    Count UTF-8 columns once, in Terminal::utf8_increment()

    outcp() wrote the codepoint with term_out() and then advanced the column itself, but term_out() -> parse_output() -> Terminal::utf8_increment()
    had already counted it -- so every codepoint outcp() emitted to a UTF-8 terminal advanced the column counter by twice the glyph's display width.
    The counter ran ahead of the cursor, making word-wrap, right-margin
    truncation, centering and the auto-pause line counter wrap early on any
    line containing such text. The call was also unconditional while
    term_out() returns early under CON_ECHO_OFF, so the column advanced for suppressed output too.

    utf8_increment() is now the only place that counts, and it passes sbbs->unicode_zerowidth rather than a hardcoded 0, preserving the
    zero-width quirk the ZWNBSP probe detects at logon. That applies the
    detected value to all UTF-8 output, bputs() included, not just outcp().

    outcp()'s inc_column() was correct when written in 9374bfe37e
    (pill-4-pencil): it paired with putcom(), which does no column
    accounting. It became a double-count in 374282bf81 (skins-28-shop),
    which switched the write to term_out() and introduced utf8_increment().

    Fixes #1200

    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 Windows@VERT to Git commit to main/sbbs/m on Saturday, July 25, 2026 23:32:00
    https://gitlab.synchro.net/main/sbbs/-/commit/005460da2b098c8ec796464a
    Modified Files:
    docs/v322_new.md src/sbbs3/useredit.cpp
    Log Message:
    Uedit: newline before the non-alpha commands that prompt

    getkeys() runs with K_NOCRLF and only alpha commands got a newline
    before their handler, so the '~' (leech), '+' (adjust credits), '*'
    (adjust minutes), '$' (credits) and '/' (AR string) prompts were echoed
    onto the tail of the "User edit (?=Menu) (n of m): " line.

    Present since ca499c9550 (silence-13-chorus) introduced the alpha-only
    newline.

    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 L@VERT to Git commit to main/sbbs/m on Tuesday, July 28, 2026 18:06:00
    https://gitlab.synchro.net/main/sbbs/-/commit/58dc4d2b39ca0c227d5427aa
    Modified Files:
    docs/v322_new.md src/sbbs3/js_global.cpp src/xpdev/ini_file.h
    Log Message:
    Correct the .local rationale: ctrl/ is not overwritten by an upgrade

    The commit that added the .local filename variation justified it as protecting a sysop's edits from being overwritten by an upgrade. That is not true of the ctrl directory, which is where most of the configuration files it applies to live: ctrl is populated when the BBS is installed and an upgrade leaves it alone. The Windows upgrade script copies exactly one file into it, text.dat.

    What an upgrade does refresh is the distributed program directories -- exec, text and docs unconditionally, and xtrn when the sysop accepts the installer's "Upgrade Externals" option -- so the overwrite argument holds for a file we ship in one of those, and for any installation run from a source checkout, where updating means pulling the whole tree.

    The variation is still worth having for a ctrl file; the honest reason is separation rather than rescue. Reworded accordingly on the wiki (config:ini_files, howto:multihost) and in the release notes, where the claim was made. xpdev is a general-purpose library, so its header documents the search order and what .local means and leaves the rationale to those.

    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 L@VERT to Git commit to main/sbbs/m on Tuesday, July 28, 2026 19:54:00
    https://gitlab.synchro.net/main/sbbs/-/commit/d91e670aaee817fea9820e9e
    Modified Files:
    docs/v322_new.md src/sbbs3/con_out.cpp str_util.c str_util.h terminal.h Log Message:
    bprintf: measure %s field widths in columns rather than bytes

    printf counts a field width in bytes, but a terminal renders columns.
    For a UTF-8 argument the two differ, so a byte-counted field prints
    short and whatever follows it slides left. Every columnar format string
    sent to a UTF-8 terminal is affected; message listings are the most
    visible case.

    Rewrite the numbers instead of the formatting. fmt_col_widths() walks
    the conversions in a format string, and for each %s carrying a literal
    width or precision re-states them in bytes such that printf's
    truncate-then-pad arrives at the requested number of columns: the new
    precision is the byte count of the leading N columns, and the new width
    is padded by the byte-minus-column difference. vsnprintf still does all
    the conversion. Sysop-customized text.dat files therefore keep working untouched, and output to a non-UTF-8 terminal is unchanged, the pre-pass running only for P_UTF8.

    A '*' width or precision is left alone: atcode() computes those itself
    and has already accounted for the difference, so rewriting them would compensate twice. An unrecognized conversion, a NULL argument, or a
    rewrite that would overflow the buffer all return the format string
    unmodified.

    Widths are measured with Terminal::bstrlen(), which already knows what
    occupies a column -- Ctrl-A codes, the PCBoard, Wildcat, Renegade,
    Celerity and WWIV sequences, backspace, and UTF-8 code-point widths.
    It gains an optional column limit and a byte-count out-parameter, which
    is what lets a field be truncated on a column boundary without splitting
    a multi-byte sequence.

    Issue #1204

    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 L@VERT to Git commit to main/sbbs/m on Friday, July 31, 2026 21:43:00
    https://gitlab.synchro.net/main/sbbs/-/commit/72303693ef1b8dc2c11ff006
    Modified Files:
    docs/v322_new.md src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: enforce download credits for unauthenticated file requests

    An anonymous request through the file-area virtual path
    (FileVPathPrefix) was authorized by user_can_download() alone, with no
    credit check, so every file in a directory with empty ARS downloaded
    free of charge regardless of its cost. The FTP server applies the
    credit gate to anonymous users, and issue #446 was closed with that
    being the intended policy.

    Both authorization paths now share check_file_download(), which applies
    the credit gate before the ARS check. Putting the two callers on one
    predicate is the point of the helper: the bypass existed because the
    anonymous branch and the authenticated one had drifted apart.

    The gate measures cost against user_available_credits() rather than
    user.cdt, so a user's free credits now count toward a download, as they
    already do on the Terminal and FTP servers, and as subtract_cdt()
    already assumed when charging: it spends free credits first.

    A refused anonymous request is logged at LOG_DEBUG. An insufficient
    credit refusal now reports NotEnoughCredits; both this and the existing authenticated failure log previously reported CantDownloadFromDir for
    it, naming the wrong cause.

    Fixes #1192

    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 L@VERT to Git commit to main/sbbs/m on Saturday, August 01, 2026 20:33:00
    https://gitlab.synchro.net/main/sbbs/-/commit/4de1032086f038890a75559e
    Modified Files:
    docs/v322_new.md src/sbbs3/websrvr.cpp
    Log Message:
    websrvr: load a file's cost before checking download credits (#1192)

    The credit gate added in 72303693ef (2026-07-31) compared against a
    cost that was always zero. resolve_vpath() loaded the file record at file_detail_index, and smb_getfile() skips smb_getmsghdr() entirely at
    that detail level, while cost is only ever set from the SMB_COST header
    field. Every comparison against it therefore passed: costed files
    downloaded free of charge for authenticated users as well as anonymous
    ones, while user_downloaded_file() still billed the account afterward
    (it reloads the record itself at file_detail_normal), so a user could
    go credit-negative.

    Read the header record when the directory's files can cost something. A
    free directory has none to report, since loadfile() zeroes the cost and download_is_free() short-circuits on the same DIR_FREE flag before any
    credit comparison, so the index record alone still suffices there and loadfile() is not asked to open the base twice.

    Verified against a live file base: an unauthenticated request for a 2,639,434-credit file is refused with 401, a zero-cost file in the same non-free directory returns 200, and a file in a free directory
    transfers in full.

    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 L@VERT to Git commit to main/sbbs/m on Saturday, August 01, 2026 23:40:00
    https://gitlab.synchro.net/main/sbbs/-/commit/7a0dabc5613b3f1a5e4ff8be
    Modified Files:
    docs/v322_new.md src/sbbs3/js_file.cpp
    Log Message:
    Parse an .ini once in File.iniGetAllObjects()

    It asked for each section's keys by name, and that lookup scans the file
    from the top -- so the cost was O(sections * lines). SyncArcade's lobby
    reads games.ini on every run for the cabinet titles, and at 4740 sections
    that read took two seconds on an idle machine; a loaded one paid seven,
    which reads as the lobby rescanning its ROMs when it is doing nothing of
    the kind.

    Parse the file into per-section lists once instead, the way scfglib1.c
    and scfglib2.c already read the configuration: 2005ms -> 66ms on that
    file. The section list still comes from iniGetSectionList(), so a
    duplicate section name is still collapsed to its first occurrence.

    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 Windows@VERT to Git commit to main/sbbs/m on Sunday, August 09, 2026 07:19:00
    https://gitlab.synchro.net/main/sbbs/-/commit/d02c34e2d43c073fb16fc610
    Modified Files:
    docs/v322_new.md src/sbbs3/xtrn_sec.cpp
    Log Message:
    Populate CHAIN.TXT lines 24 and 25 (call time and time used)

    These two WWIV drop-file fields have been hard-coded to 0 since the
    initial v3.00c check-in (7e3e47141a, 2000-10-10). Line 24 is the time
    of the call expressed as seconds past midnight; line 25 is the number
    of seconds the user has been on the system so far, so doors that show
    "time on" -- or recover the current time from the pair -- saw zeros.

    Line 25 uses timeon() rather than a raw now-starttime subtraction: it
    measures against the same logon timestamp line 24 is derived from, so
    the two stay consistent, and it clamps a negative result instead of
    printing it through an unsigned conversion.

    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 Windows@VERT to Git commit to main/sbbs/m on Sunday, August 09, 2026 07:19:00
    https://gitlab.synchro.net/main/sbbs/-/commit/abb64a33b74f9fcbe37e57ec
    Modified Files:
    docs/v322_new.md src/sbbs3/pcbdefs.hpp xtrn_sec.cpp
    Log Message:
    Fill in three PCBOARD.SYS fields left empty by the drop-file writer

    The struct-based PCBOARD.SYS writer added in 973b7f0a35 (2022-03-13)
    never assigned these, so each was written as whatever the value-
    initialized struct held:

    - Allowed K-Bytes for Download (offset 63) was 0, which tells a door
    the caller may download nothing. It now carries the user's
    available credits in kilobytes, the same source DOOR.SYS line 31
    and CALLINFO.BBS line 16 already use, saturated at 32767 -- the
    value PCBoard reads as "unlimited".

    - Conference Area user was in (offset 65) was 0. It now carries the
    current message group. The group number is used rather than the
    sub-board number (which offset 142 already carries) because it is
    an index into the user's group list and so always fits in a byte.

    - Language Extension (offset 80) was four NUL bytes; PCBoard writes
    four spaces when no alternate language is selected. The default is
    set in the structure definition alongside the other fields whose
    empty value is not zero.

    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 L@VERT to Git commit to main/sbbs/m on Sunday, August 09, 2026 23:03:00
    https://gitlab.synchro.net/main/sbbs/-/commit/2c49eafa1f3c5bfce980fbe4
    Modified Files:
    docs/v322_new.md src/sbbs3/xtrn_sec.cpp
    Log Message:
    Report GR in DOOR.SYS line 20 whenever ANSI is supported

    Line 20 required both ANSI and COLOR, so a caller using an ANSI
    terminal in monochrome mode was told "NG" and doors ran with ANSI
    disabled entirely.

    The monochrome distinction belongs to line 39 ("ANSI supported but
    NG mode"), which is still computed, but no door kit consults it.
    OpenDoors and xpdoor (both in this tree), MBSE's doorlib, and the
    Turbo Pascal DoorDriver Plus kit all decide ANSI solely from line
    20 -- DoorDriver Plus closes the file immediately after reading it,
    and doorlib names line 39 "allwaysN". Writers agree: WWIV emits GR
    on ANSI alone, and the published field definitions describe line 39
    as "always set to N".

    The ANSI-plus-COLOR test dates to the initial v3.00c check-in and
    was never revisited. 554c2ca395 (days-21-coins, 2020-12-11) moved
    it from useron.misc to the negotiated terminal flags for this same
    symptom -- doors losing ANSI for an ANSI-capable caller -- but kept
    the COLOR requirement.

    Affects both the 52-line and 31-line drop-file forms; line 20 is in
    the section common to both.

    Closes #1218

    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 L@VERT to Git commit to main/sbbs/m on Monday, August 10, 2026 03:17:00
    https://gitlab.synchro.net/main/sbbs/-/commit/b8332a4618efd80300b671d6
    Modified Files:
    docs/v322_new.md src/sbbs3/mailsrvr.cpp mailsrvr.h
    Log Message:
    Treat the submission ports as an MSA, not as port 25 (#1221)

    The message submission ports (587, and 465 for implicit TLS) ran the same
    code path as the SMTP transfer port, so nothing downstream could tell the
    two apart. Three consequences, all reported by Deuce:

    - An authenticated user submitting an original message to an external
    recipient was judged by the port-25 rule and refused as an illegal
    relay unless the sysop enabled ALLOW_RELAY, a server-wide option.
    - The submission ports accepted unauthenticated mail, contrary to
    RFC 6409 section 4.3, which requires a 530 error response to MAIL on
    an unauthenticated session.
    - Once ALLOW_RELAY was set, every sender check was skipped for an
    authenticated session, so a user could submit with any From: address,
    including one at a domain the system does not own. The SMTP envelope
    sender was already derived from the user record, but the RFC822 From:
    header is emitted verbatim, and is now also DKIM-signed.

    Listening sockets previously passed their protocol name through xpms as
    the callback data, and the accept loop recovered each socket's properties
    by comparing that pointer against the name it was created from. Those comparisons work only because the names differ: the compiler pools
    identical string literals, so the transfer and submission agents, both
    named "SMTP", share one pointer and cannot be told apart. Pass a
    descriptor instead, carrying the display name alongside the properties
    the accept loop wants, so identity no longer depends on the name.

    An authenticated user sending to an external address via a submission
    port is a submission rather than a relay and no longer consults
    ALLOW_RELAY, which continues to govern the transfer port. The G and M restrictions still apply on every port, so a sysop retains per-user
    control over outbound mail.

    A sender address is authorized when its domain is one of this system's
    and it resolves to the authenticated user. Both questions are answered by smtp_resolve_user(), extracted from the RCPT TO handler, so any address
    that reaches a user -- alias, real name, alias.cfg entry, sub-address
    tag, user number -- also works as their sender address, and stays correct
    as the sysop edits alias.cfg. An alias resolving off-system is a forced
    relay rather than a local address, so it authorizes nobody, and the
    DefaultUser catch-all is suppressed: it exists to make unknown recipients deliverable, and honoring it would let that one account send as any
    address at the system's domains. MAIL FROM and the From: header are both checked. A null reverse-path is accepted, per RFC 6409 section 3.2.

    A refusal suggests the account's own address rather than prescribing it,
    since several addresses may be acceptable and the likeliest cause is a
    mail client configured with a misspelling of the sender's own address.

    The transfer port is unaffected in every respect, so only sysops who have deliberately enabled a submission port see any change.

    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 L@VERT to Git commit to main/sbbs/m on Tuesday, August 18, 2026 22:04:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f8d0bd2f64b441e91fe3e662
    Modified Files:
    docs/v322_new.md src/sbbs3/mailsrvr.cpp
    Log Message:
    mailsrvr: honor ALLOW_RELAY for foreign submission sender addresses

    Since b8332a4618 (chart-3-cause, 2026-08-10), the submission ports verify
    that an authenticated user's MAIL FROM and From: address is at one of the system's domains and resolves to that user. That rejects the sysop who
    points a mail client at another BBS as a smarthost and submits as their own domain -- which worked on 587 before that commit, and still works on port 25 wherever ALLOW_RELAY is set.

    Both of Vertrauen's authenticated submissions on 2026-08-18 were refused
    this way, one of them from a domain whose SPF record already authorizes Vertrauen to send for it. The reply named an address the submitter had no reason to want, so it read as a problem with the server hostname rather than with the sender address.

    A sender address in a foreign domain is a relay, so honor the permission the sysop already granted for relaying: with ALLOW_RELAY enabled, accept one
    from an authenticated user who is not restricted from relaying (G/M). An address in one of the system's own domains must still resolve to the authenticated user, so this is no license to forge a local address -- the
    case that would lend the system's DKIM reputation to the forgery.

    RFC 6409 section 6.1 (Enforce Submission Rights) is a MAY, in a section
    titled "Optional Actions", so declining to enforce it is conformant. The authentication requirement of section 4.3, a MUST, is unaffected.

    Issue #1221 asked for sysop-selectable strictness here. ALLOW_RELAY is a
    coarse stand-in for that -- a dedicated option or an ARS (issue #107) would express it properly -- but it does pick out the systems that mean it.

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

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