Multi-file transfer fix (matches binkd sent_fls):
- After sending all data for a file, close immediately and add to
SentFiles tracking list instead of blocking on M_GOT. Next file
starts sending right away — no serial waiting.
- M_GOT/M_SKIP now match against SentFiles by filename, not the
current TxFile (which may already be on the next file).
- EOB only sent when send queue empty AND all SentFiles confirmed.
- SessionDone checks SentFiles is empty.
- Fixes bidirectional multi-file sessions that were losing files
because M_FILE for the next file arrived before previous file's
data was fully transmitted.
M_FILE receive handler (matches Argus bdrxReceData M_FILE):
- When new M_FILE arrives while receiving, finalize current file
if data is complete (send M_GOT), or skip if incomplete.
- Previously always skipped, discarding completed files.
Other changes:
- Add 'showkey' command: prints public key from configured private key
- Update keygen: show PublicKey as config comment (not parsed at system level)
- Replace daemon Sleep(100) with select() on listener socket
- Strip trailing NUL bytes from BinkP args (Argus FormatBinkPMsg adds #0)
Tested: 5-node simultaneous bidirectional (45 files SHA256 verified),
Argus/Radius 4.010 interop (48 files inbound + 5 outbound), BinkIT/sbbs,
both BinkP CRAM-MD5+CRYPT and Comet ED25519+ChaCha20 protocols.
Reworked BinkP state machine to match Argus/Radius p_binkp.pas:
- Separate CRAM OPT from capability OPT: answerer sends CRAM-MD5
challenge in its own M_NUL (FTS-1027 first message), then sends
capabilities in a second M_NUL (matches Argus bdInit line 850-856)
- Defer answerer M_ADR until originator's M_ADR received (matches
Argus bdGetInKey line 907-916)
- Store CRAM challenge as raw bytes parsed once at receive time
instead of reparsing from string (matches Argus ChallengePtr)
- Add state guard on OPT parsing failure before transitioning to
bsSendPwd (matches Argus bdWaitFirstMsg line 884 guard)
- Strip trailing NUL bytes from BinkP command args — Argus/Radius
appends #0 to every frame (FormatBinkPMsg in xBase.pas:6467),
which made CRAM hex odd-length and silently broke HexToBytes,
causing fallback to plain text auth
Tested CRAM-MD5 auth + CRYPT encryption against Argus/Radius 4.010
(inbound and outbound) and BinkIT/sbbs 2.42.
Two critical fixes for BinkP interop with Argus/Radius:
1. Protocol sniff before banner: peek first byte from inbound
connection to detect BinkP ($80+) vs Comet ('C'). Only send
Comet banner if remote speaks Comet. BinkP-only clients
(binkd, Argus) were receiving an unexpected text banner that
caused session stalls.
2. Trim CRAM-MD5 response: Argus sends a trailing space in the
M_PWD CRAM response. CompareText failed on the whitespace.
Verified against production Argus/Radius 4.010.
Tested: CRAM-MD5 auth successful with Argus, session completes.
Critical fix: outbound TCP connect moved from main loop to session
thread. Main loop was blocking on CometTcpConnect (15s timeout)
preventing inbound connections from being accepted. This caused
the daemon to miss incoming calls while polling outbound nodes.
BinkP security:
- ED25519 mutual auth: originator verifies answerer's pubkey
- Removed self-announced key fallback on answerer
- cetSessionReject fires on all auth failures (ED25519, CRAM, plain)
BinkP CRYPT (binkd-compatible):
- CRC32 stream cipher from binkd crypt.c (Roger Schlafly)
- Activates on CRAM-MD5 auth + both sides advertise CRYPT
- Follows Argus activation rules (CRAM or originator + password)
BinkP DES-CBC (Argus-compatible):
- Pure Pascal DES implementation (FIPS 46-3), 13/13 test vectors
- ENC DES/CBC negotiation via M_ERR per Argus protocol
- Key checksum verification (ECB encrypt + MD5 CRC16)
BinkP NR mode:
- Sender sends M_FILE with offset -1 (resume-capable)
- After sending, doesn't wait for M_GOT
- Activates when both sides advertise NR
Config: added DESKey per-node option for DES-CBC encryption.
Tests: test_des.pas (DES unit), test_binkp.sh (comprehensive suite).
Security:
- ED25519 mutual auth: originator verifies answerer's pubkey
against configured key before signing (MITM prevention)
- Remove self-announced key fallback: if no configured key,
fall through to NOPWD instead of trusting remote's claim
- cetSessionReject event on all BinkP auth failures
CRYPT mode (binkd-compatible):
- CRC32-based stream cipher from binkd crypt.c (Roger Schlafly)
- Activates when both sides advertise CRYPT + CRAM-MD5 auth
- Encrypts entire frames (header + body) in both directions
- Key derivation matches binkd byte-for-byte for interop
NR (Non-Reliable) mode:
- Sender sends M_FILE with offset -1 (resume-capable)
- After sending, doesn't wait for M_GOT (move to next file)
- Receiver handles offset -1 (currently treats as 0, TODO:
check for partial and respond with M_GET)
- Activates when both sides advertise NR in OPT
cetFileSkip was defined but never fired by either protocol.
Now both Comet (FINFOACK skip) and BinkP (M_SKIP) fire the event
so BBS hosts are notified when the remote skips a file.
BinkP now fires cetSessionReject with ErrorReason on all auth
failures, matching Comet's behavior. BBS hosts receive real-time
reject notifications regardless of protocol:
- ED25519 bad key / bad format
- Bad CRAM-MD5
- Bad password
Both sides now verify each other's identity, like SSH:
- Originator checks answerer's pubkey (from INIT) matches config
before signing the challenge. Prevents MITM impersonation.
- Answerer checks originator's signature against configured pubkey.
Prevents unauthorized access.
If either side has a configured key and verification fails:
HALT sent, session refused, cetSessionReject event fired.
NOPWD fallback only when NO public key is configured — normal
FidoNet first contact scenario.
New event: cetSessionReject with ErrorReason field.
When a public key is configured for a remote node and ED25519
verification fails, the session is now rejected with HALT instead
of silently falling back to passwordless (NOPWD).
Previous behavior: ED25519 fails → NOPWD fallback accepts session
New behavior: ED25519 fails + key on file → HALT, session refused
NOPWD fallback only applies when NO public key is configured for
the remote — the normal FidoNet case of first contact with an
unknown node.
New cetSessionReject event with ErrorReason field so BBS hosts
can display auth failures on WFC:
- "ED25519 bad key" — configured key doesn't match signature
- "Bad password" — CRAM-MD5 or plain password mismatch
COMET_VERSION is the clean version for handshake (Comet/1.2).
COMET_BUILDREV tracks internal build revisions.
COMET_FULLVER used for display/logging (currently same as VERSION).
Includes BBS host event additions: cetSessionNew, RemoteIP,
Direction, SlotIndex, RemoteSysOp fields on TCometEventData.
New event type cetSessionNew fires at TCP accept (before handshake)
so BBS hosts can show "Incoming connection" on WFC immediately and
allocate a node slot. cetSessionStart then fires after INIT exchange
with the FidoNet address, letting the BBS re-key the display.
New fields on TCometEventData:
- RemoteIP: IP address of remote (for WFC display, logging, banning)
- Direction: 'in' or 'out' (inbound vs outbound)
- SlotIndex: daemon session slot (for BBS node management)
- RemoteSysOp: sysop name from INIT exchange
These fields are populated on all session events (New, Start, Auth,
End) for both Comet and BinkP protocols. BinkP public API functions
accept optional RemoteIP/Direction/SlotIndex parameters with defaults
for backward compatibility.
Requested by Fastway BBS for WFC integration.
File transfer in both Comet and BinkP protocol engines now uses a
TStream provider interface (TCometFileProvider) instead of raw Pascal
file I/O. Protocol logic, framing, and wire format are unchanged.
New backing stores can be added by subclassing TCometFileProvider
without modifying any protocol code.
Validated: Linux x86-64, FreeBSD x86-64, single/multi/bidir/resume.
Replace raw Pascal file I/O (AssignFile/BlockRead/BlockWrite/Seek/
CloseFile) in both Comet and BinkP protocol engines with a TStream-
based provider interface (TCometFileProvider).
New unit cometio.pas defines:
- TCometFileProvider: abstract base with OpenForSend, CreateForReceive,
OpenForResume, FinalizeReceive, CleanupReceive, HashFile, HashStream
- TCometLocalFileProvider: TFileStream-backed implementation that
behaves identically to the previous raw file I/O
Both protocols now receive a FileIO provider at session init and use
it for all file operations. The protocol state machines, framing,
sliding window, and wire format are completely unchanged.
This decouples the protocol engines from the filesystem, enabling
future backing stores (WebSocket proxy, memory streams, etc.) by
simply subclassing TCometFileProvider — zero protocol code changes.
- Adopt standard versioning: 1.0, 1.1, 1.1-1 (major.minor-revision)
- Archive naming: comet-1.1-1-linux-x64.tar.gz (professional format)
- Move test sources from src/ to test/ directory
- Update Makefile, release script, and CI workflow to match
Add cetSessionStart, cetSessionAuth, and cetSessionEnd event firing
to both Comet and BinkP protocols so BBS software importing Comet as
a library receives the full session lifecycle via the event callback.
BBS hosts can wire this directly to websockets for real-time client
updates.
Also fix BinkP using FileExists() instead of CometFileExists() for
.FLO entry checks, ensuring case-insensitive path matching on Unix
consistent with the Comet protocol side.
CometNodelistLoadDir now parses the first-line date comment
(year + day number) instead of relying solely on the .NNN
extension, so nodelists spanning multiple years sort correctly.
Added release.sh for build-and-push workflow.
Updated .gitignore for build artifacts and test binaries.
- Fix auth bypass: reject passwordless login when password configured
- Protocol auto-detect via MSG_PEEK (BinkP vs Comet on same port)
- Zero-length file handling (empty .PKT files)
- M_SKIP on interrupted RX files and EOB cleanup
- CRAM-MD5 challenge sent first per FTS-1027
- Session-end message flush for pending M_GOT/M_SKIP
- DNS resolver fallback for systemd-resolved (netdb)
- CometTcpPeek (MSG_PEEK wrapper) for protocol sniffing
- Restore NR in OPT and binkp/1.1 version string
- Rebuild all-platform binaries and TIC files
Updated COMET.DOC, COMET.QA, COMET.SAM to version 1.01 with changelog.
Fixed DOS build: removed StrUtils dependency, guarded SO_SNDBUF for
Watt-32, added IFDEF UNIX around CometTcpRecv EINTR retry loop for
Windows. Release archives and TIC files for all 5 platforms.
FlushPendingSend now retries up to 3 times with 50ms waits for socket
writability instead of returning immediately on EAGAIN. This prevents
SendFrameAsync from silently dropping critical control frames (EOFACK,
FINFOACK, DATAACK) when the send buffer is momentarily full — which
caused mutual deadlock in bidirectional transfers over the internet.
Also: flush pending sends at the top of every main loop iteration
(not just inside NTX_XDATA), send catch-up DATAACKs after a flush
clears the pending buffer, and only reset AckCounter when the DATAACK
actually sends to prevent lost ACKs from stalling the sender's window.
Reverts the 128KB socket buffer reduction and fpSelect changes that
were masking symptoms of this underlying issue.
Fundamental fix for bidirectional transfer on all platforms:
- CometTcpSendSome: non-blocking send returning actual bytes sent
- SendFrameAsync: builds frame, sends what it can, queues remainder
in Session.PendingSend* for later flushing
- FlushPendingSend: resumes partial frame sends on next loop iteration
- ALL frame sends in CometTransfer go through SendFrameAsync —
prevents control frames (DATAACK/RPOS) from injecting bytes into
a partially-sent DATA frame
The transfer loop naturally interleaves TX and RX: when send buffer
is full (EAGAIN), TX breaks to RX, processes incoming data (draining
remote's buffer via TCP flow control), then retries the pending send.
No blocking, no select hacks, no mode switching. Works on Linux,
FreeBSD, single-threaded and multi-threaded environments.
Tested: 5MB send at 6.6 MB/s to FreeBSD, clean session.
- Daemon locks BSY for ALL remote AKAs after handshake, not just
the primary address. Prevents duplicate sessions to multi-AKA nodes.
- BSY unlock in Execute cleanup covers all paths (Comet + BinkP,
inbound + outbound). Stale BSY timer (10 min) handles crashes.
- BSY locking is daemon-only — protocol engines don't touch BSY.
BBS software can replace the daemon with its own locking scheme.
- BinkP inbound now scans BSO outbound on M_ADR for bidirectional
file transfer (same pattern as Comet inbound fix).
Tested: BinkP bidir multi-file (2+2) over internet, all M_GOT confirmed.
BinkP answerer now scans BSO outbound for all remote AKAs when
receiving M_ADR, queuing any pending files for send-back. Previously
BinkpRunInbound had an empty send queue — it could only receive.
Same pattern as the Comet inbound bidir fix in cometdaemon.pas.
Tested: BinkP bidir multi-file (2 sent + 2 received) over internet,
ED25519 authenticated, all M_GOT confirmed.
When send buffer is full (EAGAIN), select() now monitors both
readability and writability. This breaks the bidir deadlock where
both sides block waiting to write while neither reads — the kernel
processes TCP ACKs in both directions when select() sees either
condition, allowing send buffers to drain.
Previous attempts:
- Blocking sends: deadlocked in multi-threaded (Fastway sbwait)
- Write-only select: deadlocked on small buffers (FreeBSD 32KB)
- SO_SNDBUF bump: didn't prevent EAGAIN on FreeBSD
This approach works on Linux, FreeBSD, and in multi-threaded
environments regardless of TCP buffer size.
Also: MSG_NOSIGNAL is now platform-correct ($4000 Linux, $20000 FreeBSD).
Tested: 5MB bidir through Fastway (FreeBSD multi-threaded),
sent 7.0 MB/s + received 5.2 MB/s, clean session.
Root cause: MSG_NOSIGNAL=$4000 is Linux-only. FreeBSD uses $20000.
Passing the wrong flag to fpSend caused corrupted sends during heavy
bidirectional traffic on FreeBSD. Unidirectional masked the issue
due to lower throughput.
Also:
- CometTcpSendAll switches to blocking mode during sends to prevent
EAGAIN deadlock on platforms with small TCP send buffers
- CometSessionInit sets SO_SNDBUF/SO_RCVBUF to 256KB
- CometTcpSetBuffers added for programmatic buffer sizing
Tested: 5MB bidirectional Linux→FreeBSD, sent 9.5 MB/s + received
6.3 MB/s simultaneously, clean session completion.
Two issues caused bidirectional transfers to deadlock:
1. RX file state (handle, position, name) was in TCometXferFile which
is stack-local to CometTransfer — destroyed on return. Moved all RX
fields to TCometXferState which persists across calls.
2. Per-file CometTransfer exited when TxState=NTX_DONE even if an RX
file was still actively being received. The next call (end-of-batch)
would send EOB while the remote was still sending data, causing
deadlock. Added RxFileOpen check to the exit condition.
Also: log all remote AKAs during handshake (not just the first).
Tested: 5MB bidirectional over internet, ED25519+encrypted,
sent 5.2 MB/s + received 5.5 MB/s simultaneously, clean exit.
- Fix: DataBuf allocation too small for zlib compress output on
incompressible data (53-byte overrun). Caused heap corruption
crash after bidirectional transfers. Increased to +128 margin.
- Fix: daemon inbound sessions now scan BSO outbound for files to
send back to the caller (was outbound-only).
- Add SPEED_TESTS.txt with full benchmark results over internet
(Reno→Seattle, 450 miles): Comet 7.1 MB/s encrypted, bidir
5+ MB/s each way.
Release 1.01 includes:
- Fix frame desync on non-blocking sockets (EAGAIN handling)
- Auth cascade: encryption requires ED25519 authentication
- No plain text passwords in Comet protocol
- NOPWD via capability flag
- Log/event callback system for BBS embedding
- Transfer progress events for both Comet and BinkP
CometTcpSendAll would abandon a partially-sent frame when fpSend
returned EAGAIN (send buffer full on non-blocking socket). The
receiver then saw a truncated frame body, causing every subsequent
frame to be misaligned — manifesting as CRC errors and RPOS floods.
Only appeared over real networks with latency; loopback worked
because the kernel drained the send buffer instantly.
Fix: CometTcpWaitSend polls for socket writability on EAGAIN,
then retries. Also handle EINTR on both send and recv paths.
Tested: 10MB @ 11.1 MB/s over internet (Reno→Seattle),
10MB @ 16.9 MB/s on LAN to Fastway.
- Fix: encryption (X25519+ChaCha20) now requires ED25519 authentication.
Previously encryption activated whenever COPT_CRYPT was negotiated,
even with NOPWD/CRAM-MD5 — vulnerable to MITM.
- Add AuthMethod tracking (AUTH_ED25519/CRAM/NOPWD/PLAIN) to TCometSession
- Strip COPT_CRYPT from SharedCaps when auth != ED25519, communicated
via INITACK so both sides agree
- Remove plain text password support from Comet protocol (ED25519/CRAM/NOPWD only)
- NOPWD signaled via COPT_NOPWD capability flag, not password field
- Add log/event callback system for BBS embedding (cometlog.pas):
CometLogSetCallback() for log messages, CometLogSetEventCallback()
for structured session events (progress, file start/end, auth status)
- Wire progress events into both Comet and BinkP transfer loops (4/sec throttle)
- Document embedding API in COMET.DOC section 17