Skip to content

ftn_bso.js: direct-attach export to a non-archived node produces malformed BSO filename (missing dot), causing silent, permanent mail nondelivery #722

Description

@jriethoven

First of all, thank you for the previous quick fix!

Version: 0.5.1-beta (commit 571934d)

Summary

When a node has no archiveType configured, EchoMail/NetMail export falls through to moveFilesToOutgoing()'s direct-attach rename path, which produces a BSO flow-file name with the extension concatenated directly onto the basename — no separating dot (e.g. 43792ae5cut instead of 43792ae5.cut). Files created this way are invisible to the native BinkP mailer: core/binkp/bso_spool.js matches outbound files with the anchored regex

/^([0-9a-f]{8})\.(flo|clo|ilo|hlo|dlo|out|cut|iut|hut|dut)$/i

which requires the dot. The message sits in the outbound spool indefinitely, is skipped on every scheduled pull and crashmail dispatch, and no error is logged at any log level — the writer's fse.move() succeeds; it just moves the file to the wrong name.

Root cause — core/scanner_tossers/ftn_bso.js, moveFilesToOutgoing(), line 1378:

if ('.pk_' === ext.toLowerCase()) {
    const newExt = self.getOutgoingFlowFileExtension(
        exportOpts.destAddress, 'mail', exportType, exportOpts.fileCase
    );  // returns bare extension text, e.g. "cut" — no leading dot,
        // see getOutgoingFlowFileExtension(), line 230

    const newPath = paths.join(
        outgoingDir,
        `${paths.basename(oldPath, ext)}${newExt}`   // <-- missing dot
    );
    fse.move(oldPath, newPath, nextFile);
}

getOutgoingFlowFileExtension() intentionally returns the extension without a leading dot — consistent with its other call site in getOutgoingFlowFileName(), which correctly does ${controlFileBaseName}.${ext} a few lines earlier (line ~299). This call site is missing that same . separator.

Compounding issue — malformed name gets misdecoded as a different destination if manually "fixed"

The basename left over from the .pk_ temp file is a message serial number (getMessageSerialNumber(), used for the original packet name at line 209), not a net/node pair. bso_spool.js's getNodesWithPendingMail() assumes any 8-hex-char BSO basename is NNNNnnnn (4 hex digits net + 4 hex digits node). So simply adding the missing dot back in is not a safe fix — it makes the file match the scan regex, but the reader then decodes the serial number as if it were an address, producing a phantom destination (in our repro: basename 43792ae5 → decoded as net 0x4379 / node 0x2ae5 → logged as 700:17273/10981, "No host configured, skipping"). The message is silently misrouted to a non-existent node instead of just failing to send.

Suggested fix

Insert the missing dot at line 1378:

${paths.basename(oldPath, ext)}.${newExt}

This alone fixes the immediate bug, but note the correct fix for a fully-working direct-attach path also requires generating a real net/node-based basename (as getOutgoingFlowFileName() does) rather than reusing the .pk_ temp file's serial-number name — otherwise multiple unbundled messages queued to the same node before one ships would collide on identical filenames.

Reproduction (confirmed via packet capture)

Configure two or more FTN networks with a message area exporting to a node that has no archiveType configured.
Post/toss a message to that area, destined for that node.
Observe the resulting file in mail/ftn_out// — correct 8-hex-char serial number, but no dot before the extension (e.g. 43792ae5cut).
tcpdump a subsequent BinkP session to that node: no M_FILE frame is ever sent for it. QSIZE/session completes normally otherwise — no error anywhere in logs at any log level, including trace.
As a manual recovery test, renaming the file to insert the dot (43792ae5.cut) causes the next session to attempt sending it — but to a decoded address of 700:17273/10981 rather than the real destination, confirmed via "[BinkP/Caller] No host configured, skipping" in the log, with the hex math on the basename (4379/2ae5) matching exactly.
Renaming to the correct net/node basename for the intended destination (00640000.cut for 700:100/0) ships successfully — confirmed via M_GOT from the remote hub and file removal from the spool afterward.

Workaround

Setting archiveType (e.g. ZIP, assuming InfoZip's zip/unzip are installed) on the affected node in scannerTossers.ftn_bso.nodes routes export through createArcMailBundle() instead, entirely avoiding the buggy direct-attach rename path. Confirmed working — subsequent EchoMail exports crashmail immediately with no leftover file in the outbound spool.

Impact

Silent, permanent mail non-deliveryfor any message exported to a node without archiveType configured — which is the natural default for a newly-added FTN peer, since archiveType isn't required by any startup validation and the example config in the docs shows it as present but doesn't flag its absence as a problem.

Environment

  • [X ] I am using Node.js v14.x LTS or higher
  • [ X] npm install or yarn reports success
  • Actual Node.js version (node --version):v22.23.2
  • Operating system (uname -a on *nix systems):Linux bbs 7.0.14-4-pve #1 SMP PREEMPT_DYNAMIC PMX 7.0.14-4 (2026-07-07T07:27Z) x86_64 GNU/Linux
  • Revision (git rev-parse --short HEAD):571934db

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions