https://gitlab.synchro.net/main/sbbs/-/commit/0db85afb4cff6c0b9efbd74c
Modified Files:
src/xpdev/multisock.c
Log Message:
Set close-on-exec on listen sockets on POSIX (issue #1174)
A fork()ed child inherits every open descriptor, and external() closes
nothing but stdio and its pipes before execvp(), so every external program inherits each server's listening sockets. An external that outlives a server recycle then keeps those ports bound, and the rebind fails:
mail !ERROR 98 binding SMTP Transfer Agent socket to port 25: Address
already in use
This is not merely a failed rebind, which would be self-announcing. The parent's own listen sockets are still open, so the kernel keeps completing handshakes into a backlog that no longer has an accept() behind it: clients connect and then wait forever for a protocol banner. It presents as a hung server.
Mark the sockets close-on-exec where they are created, mirroring what the Windows half of xpms_add() has done since 6816ce611 (carries-19-baby, 2026-06-24). Doing it at creation, rather than closing descriptors in the child, is what covers
externals already running when a recycle happens: their copies were made
long before, and nothing done at recycle time can reach them.
fcntl() rather than SOCK_CLOEXEC: macOS has no SOCK_CLOEXEC, and passing it
to socket() on a kernel that predates support fails the call outright and
would take the listener with it. That leaves a window between socket() and fcntl() in which a concurrent exec still inherits the descriptor, which is worth accepting for sockets created during server start-up and recycle.
Accepted client sockets are left alone. They do not pin a listening port,
and on POSIX the passthru listener a socket door talks over is created by open_socket() in main.cpp, not here, so it is unaffected either way.
Co-Authored-By: Claude Opus 5 (1M context) <
noreply@anthropic.com>
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net