Files
Brad ArantandClaude Sonnet 5 1d3d30cd05 Fix epoll dispatch dropping INotify events, ConsoleServer self-deadlock, and add TLS SNI support
- INotify/Thread epoll dispatch was silently dropping every live
  core::INotify event (an isKnownINotify registry fixes it) - this had
  broken mail delivery for over a week, masked by routine restarts.
- TCPServer::lock self-deadlocked every admin-console connection via a
  plain std::mutex being re-entered; switched to recursive_mutex.
- TLS.cpp/h: SNI-based certificate selection (addSNIContext), the
  groundwork for multi-tenant TLS (qualityiron.vegas alongside
  barant.com on one APIServer process).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 17:38:37 +00:00

131 lines
4.8 KiB
C++

#ifndef __TLS_h__
#define __TLS_h__
#include "ZString.h"
#include "TLSInfo.h"
#include <openssl/ssl.h>
#include <map>
#include <string>
namespace core {
///
/// TLS
///
/// This object provides the support methods to handle TLS on the server core and
/// session environment.
///
class TLS {
public:
///
///
///
TLS();
///
/// Use this constructor when the SSL context needs to be created as when opening
/// a server TCPSocket.
///
TLS(TLSInfo *tlsInfo);
///
/// Use this constructor on creation of a new TCPSocket that needs access to
/// the generating server's SSL context.
///
TLS(SSL_CTX *ctx);
///
/// The destructor for this object.
///
~TLS();
void createContext(TLSInfo *tlsInfo);
///
/// Registers a second (or third, etc.) certificate/key pair for a
/// specific hostname on THIS SAME listening socket, via TLS's
/// Server Name Indication (SNI) extension - lets one TCPServer
/// terminate TLS for multiple unrelated domains, each with its own
/// real certificate, without needing a separate listening port or
/// process per domain. Must be called after createContext() (or the
/// TLSInfo* constructor, which calls it) has already established the
/// base `ctx` - that base context keeps serving as both the
/// fallback for hostnames not registered here AND the cert offered
/// to any client that sends no SNI hostname at all (a bare-IP
/// request, or any TLS client old enough to predate SNI). Callers
/// that never call this at all (every existing single-site
/// TLSInfo* user - BARANTMail, Zephora, ClockPlanet, and a
/// single-site APIServer config) see zero behavior change: the SNI
/// callback is only ever registered on `ctx` the first time this is
/// called, not unconditionally in createContext().
///
void addSNIContext(const std::string &hostname, TLSInfo *tlsInfo);
// A never-configured TLS object (either the listening TCPServer
// itself when constructed with tlsInfo=NULL, or a TCPSession spawned
// from one via TLS(SSL_CTX*)) must default to a real NULL here, not
// garbage - TCPSocket::receiveData()/sendData() key their TLS-vs-
// plain dispatch on this exact value (see TCPSocket.cpp).
SSL_CTX *ctx = NULL;
protected:
int receiveData(coreutils::ZString &buffer);
int sendData(const char *data, int length);
void registerSocket(int fd);
void acceptSocket();
protected:
// Set by sendData() when the last SSL_write() failed specifically
// with SSL_ERROR_WANT_READ (needs a READ to make progress - e.g.
// mid-handshake application data, or a post-handshake session
// ticket exchange) rather than any other reason. TCPSocket::
// sendNeedsRead() reads this (protected, so a subclass can reach
// it directly) to tell Socket::writeSocket()/resetSocket() not to
// busy-spin retrying the write via EPOLLWRNORM - see their own
// doc comments for the confirmed real damage (a 15GB+ debug log,
// and every affected connection dropped before ever reading the
// client's first command) this caused when unaddressed.
bool lastSendWantedRead = false;
private:
char *sip_cacert = (char *)"../testkeys/certs/pbxca.crt";
char *sip_cert = (char *)"../testkeys/certs/pbxserver.crt";
char *sip_key = (char *)"../testkeys/certs/pbxserver.key";
bool initialized = false;
SSL *ssl;
// The actual cert/key-loading + cipher/session-cache setup shared
// by both the base context (createContext(), unchanged) and every
// additional per-hostname context addSNIContext() builds - factored
// out so there's exactly one place that does this, not two copies
// drifting apart.
SSL_CTX *buildContext(TLSInfo *tlsInfo);
// Owns every SSL_CTX* built by addSNIContext() (the base `ctx` is
// owned/freed the same way it always was, untouched by this map).
// Keyed by hostname exactly as configured - matched case-
// insensitively against the ClientHello's SNI value in the
// callback (see TLS.cpp).
std::map<std::string, SSL_CTX *> sniContexts;
bool sniCallbackRegistered = false;
// OpenSSL's own SSL_CTX_set_tlsext_servername_callback signature -
// a static member (not a free function) so it can reach the owning
// TLS instance's private sniContexts via the `arg` parameter
// (SSL_CTX_set_tlsext_servername_arg(ctx, this), set alongside the
// callback itself in addSNIContext()).
static int sniCallback(SSL *ssl, int *ad, void *arg);
};
}
#endif