• src/sbbs3/ftpsrvr.cpp

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Tuesday, August 04, 2026 22:12:21
    https://gitlab.synchro.net/main/sbbs/-/commit/795b48fcf3c4c6d6f7dbd2f5
    Modified Files:
    src/sbbs3/ftpsrvr.cpp
    Log Message:
    ftpsrvr: don't use a file record that loadfile() failed to fill

    Three sites loaded a file record and used it without checking whether
    the load had succeeded. Two of them left the record uninitialized, so a
    failed load meant reading and then freeing indeterminate memory:

    - the download credit check took the file's cost from an uninitialized
    struct, giving an arbitrary verdict, and then called
    smb_freefilemem() on its wild pointers
    - the upload resume check left the same struct uninitialized along the
    path that clears 'append' and falls through to smb_freefilemem()
    - the delete uploader check zero-initialized the record but ignored the
    result, leaving 'from' NULL for stricmp() to dereference

    loadfile() re-opens the message base, so it can fail even though
    findfile() has just succeeded: a lock timeout, or the record removed by
    another session in between.

    Each site now reports the failure and refuses the request. The record's
    'from' pointer is checked before use as well, since
    set_convenience_ptr() assigns it only when the header carries a SENDER
    field: it can be NULL after a successful load, and the existing
    "unknown user" fallback dereferenced it to test for an empty string.

    The 'filedat' guards around two of those loads go with them. It is
    assigned once and the request is already refused where it is false, so
    the guarded fallback that charged by file length could never run, and
    the guard's only remaining effect was to let an unloaded record reach
    the checks.

    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 Tuesday, August 04, 2026 22:12:22
    https://gitlab.synchro.net/main/sbbs/-/commit/f650fc4d0d59745c52173537
    Modified Files:
    src/sbbs3/ftpsrvr.cpp
    Log Message:
    ftpsrvr: reset filedat for each RETR/SIZE/MDTM/DELE command

    filedat records whether the requested file was found in the directory's database. It is assigned only in the branch that serves a file base
    directory, but read further down in code common to every branch,
    including those serving a QWK packet, a generated index file, or an
    aliased local path.

    A session that had already run a file base command therefore left it
    set, so a later DELE of one of those non-database files reached
    removefile() with a stale true and removed whichever record in the
    session's current directory happened to carry the deleted file's name.
    An invalid directory number is rejected by smb_init_dir(), so this
    deleted a record rather than crashing.

    The other per-command flags (getdate, getsize, delecmd) are cleared
    where the command is recognized. Clear filedat with them.

    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 Monday, October 26, 2026 00:09:03
    https://gitlab.synchro.net/main/sbbs/-/commit/b73ac2b9c7affe55b51c47c5
    Modified Files:
    src/sbbs3/ftpsrvr.cpp
    Log Message:
    ftpsrvr: pack QWK and import REP packets in-process via qwkjob

    A QWK download (RETR <qwk-id>.qwk, and SIZE/MDTM of that name) used to
    create a data/pack<user#>.<host>.now semaphore and poll for up to
    QwkTimeout seconds while the Terminal Server's event thread built the
    packet. An FTP-only instance, or one whose event thread was busy or
    disabled, timed out with "451 Time-out waiting for packet creation."
    The control thread now calls qwkjob_pack() itself:

    - no new messages: 550 (unchanged); logged-in at a terminal node: 550
    Logged-in elsewhere
    - the user's pack lock is held (another session, script or host is
    packing or importing for the same user): immediate 451 "Packet
    creation already in progress" (unchanged text)
    - the per-process job cap is reached: retry once a second for up to
    QwkTimeout, then the unchanged time-out 451, so QwkTimeout keeps its
    meaning as the longest a RETR waits
    - if the client disconnects, the pack still completes and the packet
    stays in data/file/ for the next RETR

    A REP upload (STOR <qwk-id>.rep) is now received into <temp>/rep/<user#>.<socket>.rep and imported by the receive thread
    after the file is closed and before the 226, instead of being left in data/file/ for the event thread. A packet that fails to import is kept
    as data/file/<user#>.rep.<hex-time>.bad (an empty one is removed) and
    the client still gets 226, since uploading it again would not help.
    When the user's lock is held or the job cap is reached, the REP is
    queued for the event thread's sweep as data/file/<user#>.rep, or <user#>.<hex-time>.rep if a REP is already pending; temp and data can be
    on different volumes, so a failed rename falls back to a copy under a
    name the sweep ignores, then a rename. The control connection's
    inactivity clock is reset after the import.

    QWK job log lines are prefixed "ftp" and go through the server's own
    log function (so LOG_ERR lines reach error.log and MQTT).

    Co-Authored-By: Claude Opus 5.5 <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 Monday, October 26, 2026 00:09:03
    https://gitlab.synchro.net/main/sbbs/-/commit/7dd262f012ca4801e1322c22
    Modified Files:
    src/sbbs3/ftpsrvr.cpp
    Log Message:
    ftpsrvr: only treat the QWK reply packet upload as a REP

    The receive thread decided an upload was a QWK reply packet from its
    directory number (-1), but a sysop's local file system STOR/APPE is
    also received with directory -1. Such an upload was then run through
    the REP import: moved to data/file/<user#>.rep.<hex-time>.bad, deleted
    if empty, or queued as data/file/<user#>.rep (or imported, if it was a
    valid REP), so the file vanished from its destination while the client
    got "226 Upload complete". The STOR <qwk-id>.rep branch now marks the
    transfer explicitly and passes that to filexfer().

    Also:
    - Stage a cross-volume copy of a queued REP as <user#>.<socket>.tmp
    rather than .reptmp: on Windows "*.rep" also matches 8.3 short names,
    whose extension for .reptmp is .REP, so the sweep could pick up a
    partial copy.
    - Report EEXIST when no free queue name is found, instead of whatever
    errno was left over.
    - Reset the control connection's inactivity clock after a QWK pack, so
    a pack that takes longer than MaxInactivity doesn't drop the session
    on the next read.

    Co-Authored-By: Claude Opus 5.5 <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 Monday, October 26, 2026 00:09:03
    https://gitlab.synchro.net/main/sbbs/-/commit/8371e30134a3dd298c731205
    Modified Files:
    src/sbbs3/ftpsrvr.cpp
    Log Message:
    ftpsrvr: keep a REP import off the control thread's stack

    receive_thread() imports a received REP before its 226 reply, which can
    take a while, and read the user number and alias through xfer->user,
    which points into ctrl_thread()'s stack. When the server is going down
    (ftp_set is NULL), ctrl_thread() waited only 2 seconds for the transfer
    before returning, leaving the import to read a dead stack frame and, after
    it, to write *xfer.lastactive there.

    The transfer now carries the user number and alias by value for the
    import, the post-import activity stamp is skipped during shutdown like
    the existing *inprogress update, and ctrl_thread() keeps waiting (up to
    60 seconds) while the transfer is in progress. The receive thread is
    counted in thread_count, so the server's cleanup still waits for it.

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

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