Commit Graph
128 Commits
Author SHA1 Message Date
Brad Arant cfc0b19418 Fix stuck-write give-up path spinning forever instead of actually closing
shutdown() only sets shutDown=true and expects the fifo to drain
normally before eventReceived()'s keepAlive check
(!shutDown || (fifo.size() > 0 && !readHangup)) lets the connection
close - correct for a graceful QUIT/LOGOUT reply that just needs a few
more successful writes to flush, but exactly backwards for the give-up
path: the whole reason that path was reached is that the front of the
fifo can never be sent. Leaving it queued meant keepAlive stayed true
forever, resetSocket() kept re-arming EPOLLWRNORM because fifo wasn't
empty, the very next write attempt hit the identical stall
immediately, and shutdown() got called again - forever, at whatever
rate the kernel could fire EPOLLWRNORM.

Confirmed live on a real internet-facing server (Zephora's game
server, an ordinary port-scanner connection was enough to trigger it):
one socket logged "consecutive writes made zero progress - giving up
and shutting down" 2.4 million+ times in under 4 milliseconds,
pegging a worker thread at 100% CPU indefinitely. The give-up path now
clears the undeliverable fifo entry before calling shutdown(), so
keepAlive correctly evaluates to just !shutDown (now false) on the
next cycle and the connection actually tears down.
2026-09-06 19:41:07 +00:00
Brad Arant bee8b92a6d Fold command names to uppercase at registration, not just at lookup
CommandList::processRequest() upper-cases the incoming keyword before
looking it up, but add() stored whatever case the caller passed in
verbatim - any registration not already uppercase could then never be
matched by any input at all, since upper-casing a non-uppercase string
cannot produce a match against a non-uppercase key. This was invisible
on BARANTMail/APIServer (their own commands - SMTP, POP3/IMAP, the
admin console - all happen to already be registered uppercase) but
confirmed, via a full Zephora game client/server protocol comparison,
to make literally every Zephora game command unreachable, including
the base TCPServer's own publish/subscribe/event/etc. registrations
that the whole subscription layer depends on. Folding at add() time
keeps registration and lookup permanently in sync regardless of
caller case; already-uppercase callers are unaffected (idempotent).
2026-09-06 18:39:30 +00:00
Brad Arant 381ed86875 Untrack scratch .pre-* backup files accidentally swept in by the tls merge commit
The merge commit (3609ed6) used git add -A to handle the large number
of deletes/renames from conflict resolution, which also picked up 22
untracked *.pre-* files - local safety-copy backups made before
editing tracked files, never meant to be committed. Untracked (kept on
disk) rather than deleted, and added *.pre-* to .gitignore so this
does not recur.
2026-09-06 15:35:09 +00:00
Brad Arant 3609ed603e Merge tls into master: bring in subscription ownership, keep all TLS/stability work
tls and master diverged from a July 2024 fork point and never
reconciled - tls carries ~19 months of real socket/TLS stability
fixes (uninitialized tlsInfo, TCPServer::connectionRequest()
unconditionally initializing TLS, the WANT_READ write busy-spin, the
console authentication fix from this session), while master
independently picked up a Subscription/SubscriptionManager rework
(MString-based ownership tracking: owner/ownership/selection/time
fields and a working subscriptionOwner() method) that tls never had.

Every file both branches touched was resolved by keeping tls's side
entirely (Command.h, Socket.cpp, TCPServer.h, TCPSession.cpp/h,
TCPSocket.h were pure reformatting on master's side, or an
alternate/superseded fix for a bug tls already fixed a different way -
e.g. master's TCPSession uuid/alias handling vs tls's existing
uuid_unparse-based fix), with two deliberate exceptions:

- TCPServer.cpp's constructor was missing the
  commands.add(subscriptions, "publish"/"subscribe"/"catalog"/...)
  wiring master had - without it, SubscriptionManager existed but was
  completely unreachable through the normal command dispatch. Restored
  on top of tls's TLS-aware constructor.

- SubscriptionManager.cpp's "invite" command handler had a real
  incompatibility: master's version assigned into an `invitee` member
  typed as coreutils::MString via a stray `(coreutils::MString *)`
  cast that doesn't match the member's actual (non-pointer) type, and
  called session.server.getSessionByAlias() against a TCPServer
  reference - tls made TCPSession::server a pointer (part of removing
  the separate TCPSession2 class), so that call site no longer
  compiles as written. Took tls's cleaner local-variable version
  instead (functionally identical - looks up a session by alias and
  forwards an invite message) and removed the now-unused `invitee`
  member from SubscriptionManager.h.

One additional compile fix needed after resolving conflicts:
Subscription::subscriptionOwners is an MString (master's ownership
design), but TCPSession::alias is void* (tls's identity mechanism,
unchanged and depended on elsewhere) - MString has an implicit
constructor from void* already used successfully elsewhere in this
same file (the "publish" handler's Subscription constructor call),
but operator== doesn't accept the same implicit conversion, so
removeSessionSubscriptions()'s ownership check needed an explicit
coreutils::MString(session.alias) construction to compile.

Also drops output/main (a stray checked-in compiled binary, already
removed on tls) and the doxygen docs/html output (already removed on
tls, was carried on master since the fork).

Verified: full ServerCore rebuild (./compile) succeeds with zero
errors or warnings beyond the one compile fix documented above.
2026-09-06 13:09:15 +00:00
Brad Arant 1a15f6ced8 Add real authentication to ConsoleServer/ConsoleSession, fix CommandList help
ConsoleSession's login flow never actually checked anything: the
entered username was discarded and the password was never compared,
so any input (including blank) reached the command prompt. Confirmed
live via an unauthenticated login against BARANTMail's admin console
on port 1027, which was reachable externally. ConsoleServer now takes
an optional credentialChecker callback consulted by the WAIT_PASSWORD
state; omitting it preserves the old accept-anything behavior for any
other use of this class.

Separately, the console never prompted until the client's first line
arrived - that line was silently discarded just to kick the state
machine off, shifting every subsequent line one slot early. Harmless
under the old accept-anything login, but it broke real credential
checking outright (the typed password was compared against the typed
username, etc). ConsoleSession::onConnected() now starts the state
machine immediately on connect instead.

CommandList::processCommand() was a no-op stub - the intended
"typing help lists the registered commands" body was commented out,
and even referenced a Command::getName() that was never added to the
base class. The map's own key from add() already is the registered
name, so this just walks it directly.
2026-09-06 04:05:59 +00:00
Brad Arant 2676aaf84c Fix TLS write busy-spin on SSL_ERROR_WANT_READ
A TLS sendData() failing with SSL_ERROR_WANT_READ (mid-handshake
application data, or a post-handshake session ticket exchange) has
nothing to do with write-readiness, but resetSocket() kept re-arming
EPOLLWRNORM anyway - an already-writable TCP socket fires that
immediately, so the retry loop spun the CPU as fast as it could
until the connection was eventually dropped, without ever reading
the peer's first command. Confirmed via a 15GB+ debug log from one
stuck connection. sendNeedsRead() lets Socket now tell the
difference and wait for a real EPOLLIN instead of busy-retrying.
2026-09-06 04:05:40 +00:00
Brad Arant c3e8d70774 Fix numerous thread/socket lifecycle crashes and TLS/epoll write-stall busy-loop
Thread/EPoll lifecycle:
- Thread::~Thread() now actually joins/frees its worker thread (was
  empty - the OS thread kept running against already-freed EPoll/Socket
  objects on any early teardown, a confirmed "double free" crash)
- EPoll::~EPoll() calls stop() if never explicitly stopped, for the
  same reason
- epoll_wait() now uses a 1s timeout instead of blocking forever, so a
  worker thread can actually notice isStopping() and let itself be
  joined
- EPoll::processCommand() iterated threads by value, silently
  double-freeing the real vector's Thread the moment each copy went
  out of scope
- ThreadScope gained onThreadStart()/onThreadEnd() hooks (actually
  wired up now - the constructor that was supposed to store the scope
  discarded it) for per-thread C-library init (e.g. MySQL client)

Socket/TCPSocket/TLS:
- Socket::writeSocket(): give up and shut down a connection after 50
  consecutive zero-progress sends instead of retrying forever -
  SSL_ERROR_WANT_READ on a write needs a READ to make progress, but
  resetSocket() re-arms EPOLLWRNORM regardless, which an
  already-writable TCP socket satisfies immediately - a real stuck
  connection generated 55GB, then 12.9GB of debug log this way before
  being caught
- Socket::writeSocket() now honors sendData()'s actual return value
  (previously discarded, entry popped unconditionally after one call) -
  a large response could get silently truncated past what fit in one
  write()/SSL_write() call
- TLS::registerSocket()/sendData()/receiveData(): `initialized` now
  only goes true once SSL_set_fd() has actually succeeded, and
  sendData()/receiveData() check it first - fixes a real SIGSEGV where
  a new session's own queued greeting could reach SSL_write() on a
  different epoll thread before this session's handshake setup had run
- TCPServer::connectionRequest() now performs the TLS handshake ON THE
  ACCEPTED SESSION, not the listening server itself (was building SSL
  state on the wrong object, leaving every session's own `ssl`
  uninitialized)
- TCPServer::receiveData() always uses the plain (non-TLS) read path -
  the listening socket is never itself a TLS peer
- TCPSocket's block-mode literal reading used an unset `blockLength`
  field instead of `blockSize`, and could busy-spin forever on a
  multi-packet literal
- TCPSocket line scanning replaced an unbounded strcspn() (assumes a
  NUL-terminated buffer; read past a non-NUL-terminated receive buffer
  into adjacent heap memory) with a bounds-checked memchr() scan

Protocol/session handling:
- CommandList::processRequest() now case-folds the command keyword
  before lookup - SMTP/POP3/IMAP commands are spec-required to be
  case-insensitive; a client sending "Mail From:" instead of
  "MAIL FROM:" previously failed to dispatch at all
- TCPSession::protocol() no longer drops blank lines while a handler
  has grabbed input (e.g. __SMTP_DATA reading a message body, an IMAP
  APPEND literal) - those handlers depend on seeing blank lines as
  real content, not commands; this silently corrupted any message body
  containing a blank line
- TCPSession's uuid alias fixed: streaming a raw uuid_t via operator<<
  triggered libstdc++'s unsigned-char* overload (strlen() on 16 binary
  bytes, a real stack-buffer-overflow read), and `alias` pointed at a
  stack-local that was already destroyed by the time the constructor
  returned
- INotify::processEvent() only treats event->name as a NUL-terminated
  string when event->len > 0 (per inotify(7)) instead of unconditional
  strlen() on a field the kernel doesn't guarantee is even present -
  a confirmed heap-buffer-overflow read that had been misdiagnosed as
  a MySQL thread-safety issue
2026-09-03 05:17:41 +00:00
Brad ArantandClaude Sonnet 5 a51a486560 Fix uninitialized tlsInfo causing random SSL_read/write on plain sockets
TCPSocket's SSL_CTX*-taking constructor - the one every accepted
TCPSession actually goes through (TCPSession(EPoll&, TCPServer*, ...)
passes server->ctx into it) - never initialized the `tlsInfo` member,
and TCPSocket.h gave it no default either. Every session's `tlsInfo`
was uninitialized garbage.

receiveData()/sendData() key their TLS-vs-plain dispatch on
`if(tlsInfo)`. Since that garbage value is non-zero often enough on a
real stack, sessions on a plain, non-TLS server were intermittently
routed through TLS::receiveData()/sendData() (SSL_read()/SSL_write())
against an also-uninitialized SSL*, segfaulting inside libssl. Confirmed
via dmesg: repeated "general protection fault ... in libssl.so.3",
signal 11, non-deterministic timing consistent with depending on
whatever garbage happened to be on the stack for a given session.

Fixed by dispatching on `ctx` instead of `tlsInfo`. `ctx` (TLS.h) now
defaults to NULL (previously also uninitialized) and is the value that
actually gets set for real in TLS::createContext() and correctly
inherited by every session via TLS(SSL_CTX*) - unlike `tlsInfo`, which
is only ever set on the listening TCPServer's own constructor and never
propagated to sessions at all. For the previously-working TLS-enabled
case this is a no-op: ctx and tlsInfo were always both set (or both
unset) together on the listening server, so the observable behavior
there is unchanged - only the previously-undefined session-level
behavior is fixed.

Verified live: restarted the APIServer test instance (a plain, non-TLS
TCPServer - the exact configuration that was crashing), ran 100
sequential requests with no failures, and confirmed no new libssl
segfaults in dmesg since the fix (three separate pre-fix crash
incidents are all still visible in the dmesg history for comparison).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 18:56:27 -07:00
Brad ArantandClaude Sonnet 5 dfb33c0f16 Fix TCPServer::connectionRequest() unconditionally initializing TLS
connectionRequest() had a literal "if(true) { registerSocket(...);
acceptSocket(); }" guarding the TLS handshake setup for every accepted
connection, regardless of whether this TCPServer was ever given a
TLSInfo. TLS::createContext() only sets `ctx` when tlsInfo is non-NULL
(correctly guarded there) - so for any plain, non-TLS TCPServer, `ctx`
is left uninitialized, and this unconditional call reached
TLS::registerSocket() -> SSL_new(ctx) with garbage, segfaulting the
whole process on the very first connection.

Found while building APIServer (a new non-TLS HTTP server on top of
TCPServer) - confirmed via gdb (SIGSEGV inside SSL_new, called from
TLS::registerSocket, called from this exact line) that this affects ANY
non-TLS ServerCore-based server's first accepted connection, not
anything specific to the new code being tested against it.

Fixed by checking `tlsInfo` (already a TCPSocket-protected member this
class inherits) instead of the unconditional `true` - matches the guard
TLS::createContext() already uses correctly for the same distinction.

Verified live: before the fix, a non-TLS ApiServer segfaulted on the
first curl request every time; after, GET/HEAD/POST requests across
several real routes complete correctly and the server stays up for
repeated requests, including one that deliberately throws (confirming
the crash is really gone, not just less frequent).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 11:28:59 -07:00
Brad Arant 989aee9482 Normalize line endings to LF on the files touched by the TLS fix
The previous commit picked up CRLF line endings from a Windows-side
working copy used during development. Converting to plain LF to match
the rest of this Linux-native repo - content unchanged, this is purely
a line-ending normalization (932 insertions/932 deletions, 1:1).
2026-08-22 19:52:14 -07:00
Brad Arant 701d03dcd9 Fix TLS read/write dispatch on tls branch
TCPSocket inherits from both Socket and TLS, both declaring receiveData()
as unrelated sibling methods - only Socket::receiveData was virtual, so
TLS::receiveData (SSL_read) was never reached regardless of TLS config,
and even when reached its result was never forwarded to onDataReceived
(that call was commented out). Added an explicit TCPSocket::receiveData
override dispatching to TLS::receiveData or Socket::receiveData based on
tlsInfo, and completed TLS::receiveData to actually return/forward the
decrypted bytes.

Write path did not exist at all. Added Socket::sendData() as a new
virtual hook mirroring receiveData, routed Socket::writeSocket() through
it instead of a raw ::write(), added TLS::sendData() (SSL_write, same
error-handling style as the existing TLS methods), and TCPSocket::sendData()
dispatching the same way the read side does.

Also fixed: a queued write in writeSocket() was being popped off the fifo
as if sent even when shutDown was true and nothing was actually written;
removed a dead empty if(tlsInfo){} stub in onDataReceived, no longer
needed now dispatch happens one level up; replaced leftover debug
std::cout lines with proper Log() calls matching the rest of the file.

Verified via full local compile: all 20 .cpp files build clean, link
into libServerCore.a, and nm confirms the new/fixed symbols are correctly
defined in the right object files.
2026-08-22 19:51:08 -07:00
Brad Arant b5f4947f7c minor format change, 2025-06-05 16:54:36 +00:00
Brad Arant 1d16d0c17f merge conflicts resolution with develop->master. 2025-06-05 16:07:50 +00:00
Brad Arant 4230734ee6 Remove intellisense work files. 2025-06-05 16:03:53 +00:00
Brad Arant 5feabd0fde TLS almost connects. Needs read and write logic finished. 2025-02-20 15:33:28 -08:00
Brad Arant e0c0e2c07e Some work - trying to catch up to where I was. 2025-02-19 16:18:50 -08:00
Brad Arant 96e73d6341 some testing stuff. 2025-02-17 15:22:01 -08:00
Brad Arant df0e7cd6da some TLS work. 2025-01-28 16:26:40 -08:00
Brad Arant f9c12f4ba2 Adding testing functions. 2024-07-30 16:44:33 -07:00
Brad Arant e9cf447512 TCPSession2 is gone. publish commands are gone but usable if you put them in your server. 2024-07-30 16:22:36 -07:00
Brad Arant a391882541 Made server a pointer for eliminating TCPSession2. 2024-07-30 15:59:25 -07:00
Brad Arant 6c7fc7d28f Minor documentation changes. 2024-07-11 15:45:33 -07:00
Brad Arant 6ded352265 Fixed merge for some reason. 2024-07-10 13:39:55 -07:00
Brad Arant b7deabc5dc Rework for TLS. 2024-07-10 13:30:18 -07:00
brad Arant 48e2c27c3c Wierd reason after pull of new branch. 2024-07-09 20:07:59 -07:00
Brad Arant 519faa535b Beginning of TLS workings. 2024-07-09 17:07:04 -07:00
Brad Arant b2bdb522b3 Minor code cleanup. 2024-07-09 15:01:53 -07:00
Brad Arant fa3ab3f110 Adjusted code to C++23. 2024-07-09 14:30:24 -07:00
Brad Arant bbf904729f Merge branch 'master' into develop 2024-03-20 10:59:47 -07:00
Brad Arant 8b6d29b728 Delete hex files for ipch and bin files. 2024-01-18 15:37:43 -08:00
Brad Arant b04664b213 Do I need this? 2024-01-18 15:34:31 -08:00
Brad Arant 57f47fdecb Convert Subscription and SubscriptionManager to MString. 2023-10-26 18:28:51 -07:00
Matt Arant 3a75e920d9 Servercore Changes 2023-10-06 18:31:22 +00:00
Brad 9a39b6224c *NEXT/*DIE 2023-06-19 23:22:46 +00:00
Brad Arant 48be555724 fixed prolem with SessionFilter. 2023-06-12 15:25:03 -07:00
Brad Arant 043e725035 added sendToAll on Subscriptions that allows a SessionFilter object to be used to filter the list. 2023-06-12 15:15:12 -07:00
Brad Arant 29c49cc1dd set *owner pointer to session requesting publish. 2023-06-12 15:12:32 -07:00
Brad Arant cb2a61bed9 Added a method to send to all subscribers on subscription. 2023-06-12 12:41:14 -07:00
Brad Arant e78f0cd4f8 Fixed subscription handler logic. 2023-06-12 08:52:44 -07:00
Brad Arant b4fd28e41a Repaired subscription functions with new Subscription object parameters. 2023-06-11 20:21:49 -07:00
Brad Arant 5f61a2280d attempt to delete the documentation from repository since its generated. 2023-06-11 15:03:25 -07:00
Brad Arant 2bca178a06 Deleted html stuff and trying to ignore it. 2023-06-11 14:15:02 -07:00
Brad Arant 8c9f4117ae Cleaned up the subscription handler stuff.' 2023-06-10 19:27:22 -07:00
Brad Arant b40cb9f5a4 Added support for the SubscriptionHandlerFactory object. 2023-06-10 16:01:56 -07:00
Brad Arant 0ef782a11c Added subscription to handler list so subscription can be accessed from handler. 2023-06-09 20:16:04 -07:00
Brad Arant cfa70c5863 Fixed multithread sync issue and duplicate thread on termination. 2023-03-11 21:55:33 -08:00
Brad Arant b0118cfc56 subscriber work 2023-03-10 12:55:46 -08:00
Brad Arant 9b9e12d03c session 2023-03-08 13:56:52 -08:00
Brad Arant b63b4785cf Synced up to Matts code. 2023-02-11 17:24:25 -08:00
Brad Arant d8eb3d7bb2 merged in publish handlers. 2023-02-09 11:38:09 -08:00