From 381ed86875d046ed700427f7788892b7ad3b398a Mon Sep 17 00:00:00 2001 From: Brad Arant Date: Sun, 6 Sep 2026 15:35:09 +0000 Subject: [PATCH] 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. --- .gitignore | 2 +- CommandList.cpp.pre-helplist-fix-20260906 | 64 ---- ConsoleServer.cpp.pre-admin-console-20260905 | 21 -- ConsoleServer.h.pre-admin-console-20260905 | 41 --- ConsoleSession.cpp.pre-admin-console-20260905 | 81 ----- ConsoleSession.h.pre-admin-console-20260905 | 41 --- Socket.cpp.pre-exceptionsafety-20260828 | 219 ------------ Socket.cpp.pre-partialwrite-fix-20260831 | 253 -------------- Socket.cpp.pre-wantread-fix-20260905 | 325 ------------------ Socket.cpp.pre-writestall-fix-20260903 | 310 ----------------- Socket.h.pre-wantread-fix-20260905 | 238 ------------- Socket.h.pre-writestall-fix-20260903 | 221 ------------ TCPServer.cpp.pre-tlsaccept-fix-20260831 | 131 ------- TCPServer.h.pre-tlsaccept-fix-20260831 | 151 -------- TCPSocket.cpp.pre-iolock-fix-20260831 | 109 ------ TCPSocket.cpp.pre-tlsaccept-fix-20260831 | 104 ------ TCPSocket.cpp.pre-wantread-fix-20260905 | 185 ---------- TCPSocket.h.pre-iolock-fix-20260831 | 159 --------- TCPSocket.h.pre-tlsaccept-fix-20260831 | 141 -------- TCPSocket.h.pre-wantread-fix-20260905 | 178 ---------- TLS.cpp.pre-optional-mtls-20260831 | 201 ----------- TLS.cpp.pre-wantread-fix-20260905 | 280 --------------- TLS.h.pre-wantread-fix-20260905 | 73 ---- 23 files changed, 1 insertion(+), 3527 deletions(-) delete mode 100644 CommandList.cpp.pre-helplist-fix-20260906 delete mode 100644 ConsoleServer.cpp.pre-admin-console-20260905 delete mode 100644 ConsoleServer.h.pre-admin-console-20260905 delete mode 100644 ConsoleSession.cpp.pre-admin-console-20260905 delete mode 100644 ConsoleSession.h.pre-admin-console-20260905 delete mode 100644 Socket.cpp.pre-exceptionsafety-20260828 delete mode 100644 Socket.cpp.pre-partialwrite-fix-20260831 delete mode 100644 Socket.cpp.pre-wantread-fix-20260905 delete mode 100644 Socket.cpp.pre-writestall-fix-20260903 delete mode 100644 Socket.h.pre-wantread-fix-20260905 delete mode 100644 Socket.h.pre-writestall-fix-20260903 delete mode 100644 TCPServer.cpp.pre-tlsaccept-fix-20260831 delete mode 100644 TCPServer.h.pre-tlsaccept-fix-20260831 delete mode 100644 TCPSocket.cpp.pre-iolock-fix-20260831 delete mode 100644 TCPSocket.cpp.pre-tlsaccept-fix-20260831 delete mode 100644 TCPSocket.cpp.pre-wantread-fix-20260905 delete mode 100644 TCPSocket.h.pre-iolock-fix-20260831 delete mode 100644 TCPSocket.h.pre-tlsaccept-fix-20260831 delete mode 100644 TCPSocket.h.pre-wantread-fix-20260905 delete mode 100644 TLS.cpp.pre-optional-mtls-20260831 delete mode 100644 TLS.cpp.pre-wantread-fix-20260905 delete mode 100644 TLS.h.pre-wantread-fix-20260905 diff --git a/.gitignore b/.gitignore index 49dca78..2421255 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ docs/html */mmap_address.bin .history/* html -latex \ No newline at end of file +latex*.pre-* diff --git a/CommandList.cpp.pre-helplist-fix-20260906 b/CommandList.cpp.pre-helplist-fix-20260906 deleted file mode 100644 index 35816e3..0000000 --- a/CommandList.cpp.pre-helplist-fix-20260906 +++ /dev/null @@ -1,64 +0,0 @@ -#include "CommandList.h" -#include "Log.h" -#include -#include - -namespace core { - - CommandList::CommandList(std::string delimiter, int depth) : delimiter(delimiter), depth(depth) {} - - void CommandList::add(Command &command, std::string name) { - commands.insert(std::make_pair(name, &command)); - } - - void CommandList::remove(Command &command) {} - - int CommandList::processRequest(coreutils::ZString &request, TCPSession &session) { - if(session.grab != NULL) { - return session.grab->processCommand(request, session); - } - else { - if(request.equals("")) - return 0; - request.split(delimiter, depth); - request.reset(); - try { - // Every protocol built on this (SMTP, POP3, IMAP - RFC 5321 - // 2.4, RFC 1939 3, RFC 3501 2.2.1) specifies that command - // keywords are case-insensitive, but commands are always - // registered here in uppercase and this was an exact-match - // lookup - "mail from:" or "Mail From:" (both legal on the - // wire, and not unusual from real clients) silently failed - // to dispatch at all. Only the lookup key is case-folded; - // the token instances handlers actually receive are - // untouched, so any handler doing its own case-sensitive - // parsing on arguments is unaffected. - std::string key = request[0].str(); - std::transform(key.begin(), key.end(), key.begin(), - [](unsigned char c) { return std::toupper(c); }); - auto command = commands.at(key); - return command->processCommand(request, session); - } - catch(...) { - return 0; - } - } - return true; - } - - bool CommandList::grabInput(TCPSession &session, Command &command) { - session.grab = &command; - return true; - } - - void CommandList::clearGrab(TCPSession &session) { - session.grab = NULL; - } - - int CommandList::processCommand(coreutils::ZString &request, TCPSession &session) { - // for(Command *command : commands) - // session.out << command->getName() << std::endl; - return true; - } - -} diff --git a/ConsoleServer.cpp.pre-admin-console-20260905 b/ConsoleServer.cpp.pre-admin-console-20260905 deleted file mode 100644 index 2a49815..0000000 --- a/ConsoleServer.cpp.pre-admin-console-20260905 +++ /dev/null @@ -1,21 +0,0 @@ -#include "ConsoleServer.h" -#include "ConsoleSession.h" -#include "Command.h" -#include "Log.h" - -namespace core { - - ConsoleServer::ConsoleServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo) : TCPServer(ePoll, address, tlsInfo, " ", 10, "Console Server") { - coreutils::Log(this); - } - - void ConsoleServer::logSend(std::string out) { - for(auto *session : sessions) - ((ConsoleSession *)session)->writeLog(out); - } - - TCPSession * ConsoleServer::getSocketAccept(EPoll &ePoll) { - return new ConsoleSession(ePoll, this); - } - -} diff --git a/ConsoleServer.h.pre-admin-console-20260905 b/ConsoleServer.h.pre-admin-console-20260905 deleted file mode 100644 index 55f8e25..0000000 --- a/ConsoleServer.h.pre-admin-console-20260905 +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef __ConsoleServer_h__ -# define __ConsoleServer_h__ - -# include "TCPServer.h" -# include "Command.h" -# include "EPoll.h" -# include "LogListener.h" -# include "TLSInfo.h" - -namespace core { - - class TCPSocket; - class TCPSession; - - /// - /// - /// - - class ConsoleServer : public TCPServer, public coreutils::LogListener { - - public: - - // - // - // - - ConsoleServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo); - - // - // - // - - void logSend(std::string out) override; - - TCPSession * getSocketAccept(EPoll &ePoll) override; - - }; - -} - -#endif diff --git a/ConsoleSession.cpp.pre-admin-console-20260905 b/ConsoleSession.cpp.pre-admin-console-20260905 deleted file mode 100644 index ae4ef71..0000000 --- a/ConsoleSession.cpp.pre-admin-console-20260905 +++ /dev/null @@ -1,81 +0,0 @@ -#include "ConsoleSession.h" -#include "TCPServer.h" -#include "Log.h" - -namespace core { - - ConsoleSession::ConsoleSession(EPoll &ePoll, TCPServer *server) : TerminalSession(ePoll, server) {} - - ConsoleSession::~ConsoleSession() {} - - void ConsoleSession::protocol(coreutils::ZString &data) { - - coreutils::Log(coreutils::LOG_DEBUG_1) << data; - - coreutils::ZString blank(""); - - switch (status) { - - case WELCOME: - status = LOGIN; - protocol(blank); - break; - - case LOGIN: - out << "User: "; - status = WAIT_USER_PROFILE; - break; - - case WAIT_USER_PROFILE: - status = PASSWORD; - protocol(blank); - break; - - case PASSWORD: - out << "Password: "; - status = WAIT_PASSWORD; - break; - - case WAIT_PASSWORD: - status = PROMPT; - protocol(blank); - break; - - case PROMPT: - out << (": "); - status = INPUT; - break; - - case INPUT: - command = data; - status = PROCESS; - protocol(blank); - break; - - case PROCESS: - doCommand(command); - status = command.equals("exit") ? DONE: PROMPT; - protocol(blank); - break; - - case DONE: - out << "Done." << std::endl; - break; - - default: - out << "Unrecognized status code: ERROR."; - break; - } - } - - void ConsoleSession::writeLog(std::string data) { - // saveCursor(); - // setCursorLocation(16, 1); - // restoreCursor(); - } - - void ConsoleSession::doCommand(coreutils::ZString &request) { - server->commands.processRequest(request, *this); - } - -} diff --git a/ConsoleSession.h.pre-admin-console-20260905 b/ConsoleSession.h.pre-admin-console-20260905 deleted file mode 100644 index b1369ff..0000000 --- a/ConsoleSession.h.pre-admin-console-20260905 +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef __ConsoleSession_h__ -#define __ConsoleSession_h__ - -#include "TerminalSession.h" -//#include "TCPServer.h" -#include "CommandList.h" - -namespace core { - - class TCPServer; - - /// - /// ConsoleSession - /// - /// Extends the session parameters for this TCPSocket derived object. - /// Extend the protocol() method in order to define the behavior and - /// protocol interaction for this socket which is a console session. - /// - - class ConsoleSession : public TerminalSession { - - public: - ConsoleSession(EPoll &ePoll, TCPServer *server); - ~ConsoleSession(); - - void writeLog(std::string data); - - protected: - void protocol(coreutils::ZString &data) override; - - private: - enum Status {WELCOME, LOGIN, WAIT_USER_PROFILE, PASSWORD, WAIT_PASSWORD, PROMPT, INPUT, PROCESS, DONE}; - Status status = WELCOME; - void doCommand(coreutils::ZString &request); - coreutils::ZString command; - - }; - -} - -#endif diff --git a/Socket.cpp.pre-exceptionsafety-20260828 b/Socket.cpp.pre-exceptionsafety-20260828 deleted file mode 100644 index de0a03e..0000000 --- a/Socket.cpp.pre-exceptionsafety-20260828 +++ /dev/null @@ -1,219 +0,0 @@ -#include "Socket.h" -#include "EPoll.h" -#include "Exception.h" -#include "Log.h" -#include "ZString.h" - -namespace core -{ - - void sigpipe_handler(int unused) {} - - Socket::Socket(EPoll &ePoll, std::string text) : ePoll(ePoll), text(text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Socket object created [" << text << "]."; - buffer = (char *)malloc(4096); - length = 4096; - } - - Socket::~Socket() { - shutDown = true; - onUnregister(); - disableSocket(); - coreutils::Log(coreutils::LOG_DEBUG_4) << "Free on socket " << descriptor; - free(buffer); - if(descriptor == -1) - return; - close(descriptor); - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has ended (" << descriptor << ")."; - } - - void Socket::setDescriptor(int descriptor) - { - if ((descriptor == -1) && (errno == 24)) - { - shutdown("Too many files open"); - throw coreutils::Exception("Too many files open. Refusing connection."); - } - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has started (" << descriptor << ")."; - if(descriptor < 3) - throw coreutils::Exception("Descriptor out of range", __FILE__, __LINE__); - this->descriptor = descriptor; - onRegister(); - enableSocket(); - onRegistered(); - } - - int Socket::getDescriptor() - { - return descriptor; - } - - void Socket::setBufferSize(int length) - { - this->length = length; - buffer = (char *)realloc(buffer, length); - } - - int Socket::getBufferSize() - { - return length; - } - - void Socket::onRegister() {} - - void Socket::onRegistered() {} - - void Socket::onUnregister() {} - - void Socket::onUnregistered() {} - - bool Socket::eventReceived(struct epoll_event event, long long eventId) { - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor(); - if(inHandler) - coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true."; - inHandler = true; - if(event.events & EPOLLRDHUP) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP"; - readHangup = true; - shutdown("hangup received"); - } - if(event.events & EPOLLIN) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN"; - coreutils::ZString zbuffer(buffer, length); - lock.lock(); - receiveData(zbuffer); - lock.unlock(); - } - if(event.events & EPOLLWRNORM) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM"; - writeSocket(); - } - inHandler = false; - bool keepAlive = !shutDown; - - // resetSocket() re-arms this fd via EPOLL_CTL_MOD, which makes it - // immediately eligible for delivery to ANY worker thread's - // epoll_wait() - including one already blocked and about to wake - // up right away. The old code called resetSocket() from inside - // the branches above while several more statements (an - // unconditional inHandler write, this Log call, and `return - // !shutDown`) still touched `this` afterward - a second thread - // could legitimately receive the re-armed event, finish handling - // it, and delete this object (Thread::run() does exactly that - // when eventReceived() returns false) before this thread's own - // remaining statements ran, a heap-use-after-free confirmed via - // AddressSanitizer under concurrent load. resetSocket() must be - // the last thing that ever touches `this`, with the return value - // already captured in a local (`keepAlive`) beforehand. - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process ending for socket " << getDescriptor(); - if(keepAlive) - resetSocket(); - return keepAlive; - } - - void Socket::connectionRequest() {} - - void Socket::onDataReceived(std::string data) { - throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1); - } - - void Socket::onDataReceived(coreutils::ZString &data) { - onDataReceived(std::string(data.getData(), data.getLength())); - } - - void Socket::receiveData(coreutils::ZString &buffer) { - coreutils::ZString blank(""); - if(buffer.getLength() <= 0) - throw coreutils::Exception("Request to receive data with a zero buffer length.", __FILE__, __LINE__, -1); - int len; - int error = -1; - if((len = ::read(getDescriptor(), buffer.getData(), buffer.getLength())) >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - // coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer; - onDataReceived(zbuffer); - } else { - error = errno; - switch (error) { - - // When a listening socket receives a connection - // request we get one of these. - - case ENOTCONN: - connectionRequest(); - break; - - case ECONNRESET: - break; - - default: - throw coreutils::Exception("Error in read of data from socket.", __FILE__, __LINE__, error); - } - } - } - - int Socket::sendData(const char *data, int length) { - return ::write(descriptor, data, length); - } - - void Socket::writeSocket() { - outlock.lock(); - // coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor(); - if(fifo.size() > 0) { - if(!shutDown) { - sendData(fifo.front().c_str(), fifo.front().length()); - fifo.pop(); - } - } - outlock.unlock(); - } - - int Socket::write(std::string data) { - outlock.lock(); - fifo.emplace(data); - outlock.unlock(); - if(lock.try_lock()) { - resetSocket(); - lock.unlock(); - } - return 1; - } - - void Socket::output(std::stringstream &out) - { - out << "|" << descriptor << "|"; - } - - bool Socket::needsToWrite() - { - return fifo.size() > 0; - } - - void Socket::shutdown(std::string text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Shutdown requested from " << this->text << " (" << descriptor << ") with reason: " << text << "."; - shutDown = true; - reset = false; - } - - void Socket::enableSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, getDescriptor(), &event); - } - - void Socket::disableSocket() { - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, getDescriptor(), NULL); - } - - void Socket::resetSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - if(fifo.size() > 0) - event.events |= EPOLLWRNORM; - if(!shutDown) - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, getDescriptor(), &event); - } - - -} diff --git a/Socket.cpp.pre-partialwrite-fix-20260831 b/Socket.cpp.pre-partialwrite-fix-20260831 deleted file mode 100644 index 854d563..0000000 --- a/Socket.cpp.pre-partialwrite-fix-20260831 +++ /dev/null @@ -1,253 +0,0 @@ -#include "Socket.h" -#include "EPoll.h" -#include "Exception.h" -#include "Log.h" -#include "ZString.h" -#include - -namespace core -{ - - void sigpipe_handler(int unused) {} - - Socket::Socket(EPoll &ePoll, std::string text) : ePoll(ePoll), text(text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Socket object created [" << text << "]."; - buffer = (char *)malloc(4096); - length = 4096; - } - - Socket::~Socket() { - shutDown = true; - onUnregister(); - disableSocket(); - coreutils::Log(coreutils::LOG_DEBUG_4) << "Free on socket " << descriptor; - free(buffer); - if(descriptor == -1) - return; - close(descriptor); - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has ended (" << descriptor << ")."; - } - - void Socket::setDescriptor(int descriptor) - { - if ((descriptor == -1) && (errno == 24)) - { - shutdown("Too many files open"); - throw coreutils::Exception("Too many files open. Refusing connection."); - } - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has started (" << descriptor << ")."; - if(descriptor < 3) - throw coreutils::Exception("Descriptor out of range", __FILE__, __LINE__); - this->descriptor = descriptor; - onRegister(); - enableSocket(); - onRegistered(); - } - - int Socket::getDescriptor() - { - return descriptor; - } - - void Socket::setBufferSize(int length) - { - this->length = length; - buffer = (char *)realloc(buffer, length); - } - - int Socket::getBufferSize() - { - return length; - } - - void Socket::onRegister() {} - - void Socket::onRegistered() {} - - void Socket::onUnregister() {} - - void Socket::onUnregistered() {} - - bool Socket::eventReceived(struct epoll_event event, long long eventId) { - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor(); - if(inHandler) - coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true."; - inHandler = true; - if(event.events & EPOLLRDHUP) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP"; - readHangup = true; - shutdown("hangup received"); - } - if(event.events & EPOLLIN) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN"; - coreutils::ZString zbuffer(buffer, length); - // receiveData() calls all the way down through - // onDataReceived()/HTTPRequest::doParse()/application request - // handling with NO exception protection anywhere in that chain - - // confirmed live: a single thrown coreutils::Exception (e.g. a - // malformed request hitting a parse-time throw, well before - // ApiSession::handleComplete()'s own try/catch around - // match.render() ever runs) reached std::terminate() and killed - // the entire server process, not just this one connection, since - // this runs on an EPoll worker thread and C++ terminates the - // whole program on an exception escaping a thread. The old - // lock.lock()/lock.unlock() pair (no RAII) also leaked the lock - // permanently on this same path - std::lock_guard fixes that as - // a side effect of adding the try/catch here. - try { - std::lock_guard guard(lock); - receiveData(zbuffer); - } catch(coreutils::Exception &e) { - // Also to stderr, not just Log() - this process's Log class - // writes through a static listener/file neither this server - // nor its main() ever configures (its own startup banner - // uses plain std::cout, not Log at all), so a Log()-only - // message here would be silently dropped. stderr reliably - // lands in the log file given how this process is started - // (both stdout and stderr redirected to the same file). - std::cerr << "Exception processing socket " << getDescriptor() << ": " << e.text << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Exception processing socket " << getDescriptor() << ": " << e.text; - shutdown("exception during receiveData: " + e.text); - } catch(std::exception &e) { - std::cerr << "std::exception processing socket " << getDescriptor() << ": " << e.what() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "std::exception processing socket " << getDescriptor() << ": " << e.what(); - shutdown(std::string("exception during receiveData: ") + e.what()); - } catch(...) { - std::cerr << "Unknown exception processing socket " << getDescriptor() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Unknown exception processing socket " << getDescriptor(); - shutdown("unknown exception during receiveData"); - } - } - if(event.events & EPOLLWRNORM) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM"; - writeSocket(); - } - inHandler = false; - bool keepAlive = !shutDown; - - // resetSocket() re-arms this fd via EPOLL_CTL_MOD, which makes it - // immediately eligible for delivery to ANY worker thread's - // epoll_wait() - including one already blocked and about to wake - // up right away. The old code called resetSocket() from inside - // the branches above while several more statements (an - // unconditional inHandler write, this Log call, and `return - // !shutDown`) still touched `this` afterward - a second thread - // could legitimately receive the re-armed event, finish handling - // it, and delete this object (Thread::run() does exactly that - // when eventReceived() returns false) before this thread's own - // remaining statements ran, a heap-use-after-free confirmed via - // AddressSanitizer under concurrent load. resetSocket() must be - // the last thing that ever touches `this`, with the return value - // already captured in a local (`keepAlive`) beforehand. - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process ending for socket " << getDescriptor(); - if(keepAlive) - resetSocket(); - return keepAlive; - } - - void Socket::connectionRequest() {} - - void Socket::onDataReceived(std::string data) { - throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1); - } - - void Socket::onDataReceived(coreutils::ZString &data) { - onDataReceived(std::string(data.getData(), data.getLength())); - } - - void Socket::receiveData(coreutils::ZString &buffer) { - coreutils::ZString blank(""); - if(buffer.getLength() <= 0) - throw coreutils::Exception("Request to receive data with a zero buffer length.", __FILE__, __LINE__, -1); - int len; - int error = -1; - if((len = ::read(getDescriptor(), buffer.getData(), buffer.getLength())) >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - // coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer; - onDataReceived(zbuffer); - } else { - error = errno; - switch (error) { - - // When a listening socket receives a connection - // request we get one of these. - - case ENOTCONN: - connectionRequest(); - break; - - case ECONNRESET: - break; - - default: - throw coreutils::Exception("Error in read of data from socket.", __FILE__, __LINE__, error); - } - } - } - - int Socket::sendData(const char *data, int length) { - return ::write(descriptor, data, length); - } - - void Socket::writeSocket() { - outlock.lock(); - // coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor(); - if(fifo.size() > 0) { - if(!shutDown) { - sendData(fifo.front().c_str(), fifo.front().length()); - fifo.pop(); - } - } - outlock.unlock(); - } - - int Socket::write(std::string data) { - outlock.lock(); - fifo.emplace(data); - outlock.unlock(); - if(lock.try_lock()) { - resetSocket(); - lock.unlock(); - } - return 1; - } - - void Socket::output(std::stringstream &out) - { - out << "|" << descriptor << "|"; - } - - bool Socket::needsToWrite() - { - return fifo.size() > 0; - } - - void Socket::shutdown(std::string text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Shutdown requested from " << this->text << " (" << descriptor << ") with reason: " << text << "."; - shutDown = true; - reset = false; - } - - void Socket::enableSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, getDescriptor(), &event); - } - - void Socket::disableSocket() { - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, getDescriptor(), NULL); - } - - void Socket::resetSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - if(fifo.size() > 0) - event.events |= EPOLLWRNORM; - if(!shutDown) - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, getDescriptor(), &event); - } - - -} diff --git a/Socket.cpp.pre-wantread-fix-20260905 b/Socket.cpp.pre-wantread-fix-20260905 deleted file mode 100644 index b2b8fd7..0000000 --- a/Socket.cpp.pre-wantread-fix-20260905 +++ /dev/null @@ -1,325 +0,0 @@ -#include "Socket.h" -#include "EPoll.h" -#include "Exception.h" -#include "Log.h" -#include "ZString.h" -#include - -namespace core -{ - - void sigpipe_handler(int unused) {} - - Socket::Socket(EPoll &ePoll, std::string text) : ePoll(ePoll), text(text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Socket object created [" << text << "]."; - buffer = (char *)malloc(4096); - length = 4096; - } - - Socket::~Socket() { - shutDown = true; - onUnregister(); - disableSocket(); - coreutils::Log(coreutils::LOG_DEBUG_4) << "Free on socket " << descriptor; - free(buffer); - if(descriptor == -1) - return; - close(descriptor); - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has ended (" << descriptor << ")."; - } - - void Socket::setDescriptor(int descriptor) - { - if ((descriptor == -1) && (errno == 24)) - { - shutdown("Too many files open"); - throw coreutils::Exception("Too many files open. Refusing connection."); - } - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has started (" << descriptor << ")."; - if(descriptor < 3) - throw coreutils::Exception("Descriptor out of range", __FILE__, __LINE__); - this->descriptor = descriptor; - onRegister(); - enableSocket(); - onRegistered(); - } - - int Socket::getDescriptor() - { - return descriptor; - } - - void Socket::setBufferSize(int length) - { - this->length = length; - buffer = (char *)realloc(buffer, length); - } - - int Socket::getBufferSize() - { - return length; - } - - void Socket::onRegister() {} - - void Socket::onRegistered() {} - - void Socket::onUnregister() {} - - void Socket::onUnregistered() {} - - bool Socket::eventReceived(struct epoll_event event, long long eventId) { - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor(); - if(inHandler) - coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true."; - inHandler = true; - if(event.events & EPOLLRDHUP) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP"; - readHangup = true; - shutdown("hangup received"); - } - if(event.events & EPOLLIN) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN"; - coreutils::ZString zbuffer(buffer, length); - // receiveData() calls all the way down through - // onDataReceived()/HTTPRequest::doParse()/application request - // handling with NO exception protection anywhere in that chain - - // confirmed live: a single thrown coreutils::Exception (e.g. a - // malformed request hitting a parse-time throw, well before - // ApiSession::handleComplete()'s own try/catch around - // match.render() ever runs) reached std::terminate() and killed - // the entire server process, not just this one connection, since - // this runs on an EPoll worker thread and C++ terminates the - // whole program on an exception escaping a thread. The old - // lock.lock()/lock.unlock() pair (no RAII) also leaked the lock - // permanently on this same path - std::lock_guard fixes that as - // a side effect of adding the try/catch here. - try { - std::lock_guard guard(lock); - receiveData(zbuffer); - } catch(coreutils::Exception &e) { - // Also to stderr, not just Log() - this process's Log class - // writes through a static listener/file neither this server - // nor its main() ever configures (its own startup banner - // uses plain std::cout, not Log at all), so a Log()-only - // message here would be silently dropped. stderr reliably - // lands in the log file given how this process is started - // (both stdout and stderr redirected to the same file). - std::cerr << "Exception processing socket " << getDescriptor() << ": " << e.text << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Exception processing socket " << getDescriptor() << ": " << e.text; - shutdown("exception during receiveData: " + e.text); - } catch(std::exception &e) { - std::cerr << "std::exception processing socket " << getDescriptor() << ": " << e.what() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "std::exception processing socket " << getDescriptor() << ": " << e.what(); - shutdown(std::string("exception during receiveData: ") + e.what()); - } catch(...) { - std::cerr << "Unknown exception processing socket " << getDescriptor() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Unknown exception processing socket " << getDescriptor(); - shutdown("unknown exception during receiveData"); - } - } - if(event.events & EPOLLWRNORM) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM"; - writeSocket(); - } - inHandler = false; - // A graceful shutdown() (session.terminate() -> "goodbye" reply - // queued via write(), then shutdown() called - see e.g. IMAP's - // LOGOUT/POP3's QUIT/SMTP's QUIT) used to make keepAlive false - // the moment shutDown was set, in this SAME event cycle that had - // just queued that reply - Thread::run() then deletes this object - // immediately on a false return, before the reply was ever - // written to the actual socket (write() only queues into fifo; - // the real send() happens in writeSocket(), only reachable via a - // LATER EPOLLWRNORM event this object never lived to receive). - // Confirmed live: LOGOUT/QUIT connections closed correctly but - // their own final reply line never reached the client. Staying - // alive while shutDown is set but fifo still has data - as long - // as the peer hasn't already hung up (readHangup - trying to - // drain to a peer that's already gone is pointless, not graceful) - // - gives that queued reply a real chance to go out before this - // socket actually closes. - bool keepAlive = !shutDown || (fifo.size() > 0 && !readHangup); - - // resetSocket() re-arms this fd via EPOLL_CTL_MOD, which makes it - // immediately eligible for delivery to ANY worker thread's - // epoll_wait() - including one already blocked and about to wake - // up right away. The old code called resetSocket() from inside - // the branches above while several more statements (an - // unconditional inHandler write, this Log call, and `return - // !shutDown`) still touched `this` afterward - a second thread - // could legitimately receive the re-armed event, finish handling - // it, and delete this object (Thread::run() does exactly that - // when eventReceived() returns false) before this thread's own - // remaining statements ran, a heap-use-after-free confirmed via - // AddressSanitizer under concurrent load. resetSocket() must be - // the last thing that ever touches `this`, with the return value - // already captured in a local (`keepAlive`) beforehand. - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process ending for socket " << getDescriptor(); - if(keepAlive) - resetSocket(); - return keepAlive; - } - - void Socket::connectionRequest() {} - - void Socket::onDataReceived(std::string data) { - throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1); - } - - void Socket::onDataReceived(coreutils::ZString &data) { - onDataReceived(std::string(data.getData(), data.getLength())); - } - - void Socket::receiveData(coreutils::ZString &buffer) { - coreutils::ZString blank(""); - if(buffer.getLength() <= 0) - throw coreutils::Exception("Request to receive data with a zero buffer length.", __FILE__, __LINE__, -1); - int len; - int error = -1; - if((len = ::read(getDescriptor(), buffer.getData(), buffer.getLength())) >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - // coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer; - onDataReceived(zbuffer); - } else { - error = errno; - switch (error) { - - // When a listening socket receives a connection - // request we get one of these. - - case ENOTCONN: - connectionRequest(); - break; - - case ECONNRESET: - break; - - default: - throw coreutils::Exception("Error in read of data from socket.", __FILE__, __LINE__, error); - } - } - } - - int Socket::sendData(const char *data, int length) { - return ::write(descriptor, data, length); - } - - void Socket::writeSocket() { - outlock.lock(); - // coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor(); - if(fifo.size() > 0) { - // No longer gated on !shutDown: writeSocket() only ever runs in - // response to an EPOLLWRNORM event this object explicitly - // re-armed for (see resetSocket()'s own doc comment) - refusing - // to actually perform that queued write once shutDown was set - // was exactly what left a graceful QUIT/LOGOUT's own reply - // stuck in the fifo forever, never reaching the client. - { - // sendData()'s return value was previously discarded and the - // fifo entry popped unconditionally after ONE call - correct - // only by accident, since a single write()/SSL_write() call is - // never guaranteed to accept an entire buffer. A response - // small enough to fit the kernel's socket send buffer in one - // shot (true for almost everything on this site, and always - // true testing over loopback, where that buffer is - // effectively unlimited) went out fine either way - but a - // large one (confirmed with a real 208KB response, mostly an - // embedded base64 font: fine over loopback, but only ~65KB of - // it ever reached a real remote client before the rest was - // silently dropped here) left the client waiting forever for - // bytes matching its own Content-Length that were never - // coming. Now: pop only once the FULL entry has gone out; - // otherwise trim off just what was actually sent and leave - // the remainder queued - resetSocket() (called at the end of - // every eventReceived(), see its own doc comment) keeps - // re-arming EPOLLWRNORM as long as the fifo isn't empty, so - // the next write-ready event retries this same entry - // automatically. A return <= 0 (EAGAIN or a transient error) - // sends nothing this round and is handled identically - just - // leave the entry queued for the next attempt. - int sent = sendData(fifo.front().c_str(), fifo.front().length()); - if(sent > 0) { - consecutiveWriteStalls = 0; - if((size_t)sent >= fifo.front().length()) - fifo.pop(); - else - fifo.front().erase(0, sent); - } else if(++consecutiveWriteStalls > 50) { - // sendData() keeps failing for a reason that has nothing to - // do with the socket's own write-readiness (confirmed real - // case: TLS::sendData() returning SSL_ERROR_WANT_READ, - // which needs a READ to make progress, not another write - // attempt) - resetSocket() re-arms EPOLLWRNORM regardless, - // and an already-writable TCP socket makes it fire again - // immediately, so this cycle would otherwise spin forever - // doing real work at the CPU's own top speed. See - // consecutiveWriteStalls' own doc comment in Socket.h for - // the confirmed real damage (a 55GB, then 12.9GB debug log) - // this caused before being noticed. - coreutils::Log(coreutils::LOG_EXCEPT) << "Socket " << getDescriptor() << ": " << consecutiveWriteStalls << " consecutive writes made zero progress - giving up and shutting down."; - shutdown("stuck write retry loop - no progress after repeated attempts"); - } - } - } - outlock.unlock(); - } - - int Socket::write(std::string data) { - outlock.lock(); - fifo.emplace(data); - outlock.unlock(); - if(lock.try_lock()) { - resetSocket(); - lock.unlock(); - } - return 1; - } - - void Socket::output(std::stringstream &out) - { - out << "|" << descriptor << "|"; - } - - bool Socket::needsToWrite() - { - return fifo.size() > 0; - } - - void Socket::shutdown(std::string text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Shutdown requested from " << this->text << " (" << descriptor << ") with reason: " << text << "."; - shutDown = true; - reset = false; - } - - void Socket::enableSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, getDescriptor(), &event); - } - - void Socket::disableSocket() { - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, getDescriptor(), NULL); - } - - void Socket::resetSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - if(fifo.size() > 0) - event.events |= EPOLLWRNORM; - // Was `if(!shutDown)` - refused to re-arm at all once shutting - // down, even with data still queued to send (e.g. a graceful - // QUIT/LOGOUT's own final reply - see eventReceived()'s own doc - // comment on keepAlive for the full chain this broke). Re-arming - // for just long enough to drain the fifo, when the peer is still - // there to receive it (!readHangup - draining to an already-gone - // peer is pointless), is what actually lets that reply go out - // before this socket closes. - if(!shutDown || (fifo.size() > 0 && !readHangup)) - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, getDescriptor(), &event); - } - - -} diff --git a/Socket.cpp.pre-writestall-fix-20260903 b/Socket.cpp.pre-writestall-fix-20260903 deleted file mode 100644 index b526628..0000000 --- a/Socket.cpp.pre-writestall-fix-20260903 +++ /dev/null @@ -1,310 +0,0 @@ -#include "Socket.h" -#include "EPoll.h" -#include "Exception.h" -#include "Log.h" -#include "ZString.h" -#include - -namespace core -{ - - void sigpipe_handler(int unused) {} - - Socket::Socket(EPoll &ePoll, std::string text) : ePoll(ePoll), text(text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Socket object created [" << text << "]."; - buffer = (char *)malloc(4096); - length = 4096; - } - - Socket::~Socket() { - shutDown = true; - onUnregister(); - disableSocket(); - coreutils::Log(coreutils::LOG_DEBUG_4) << "Free on socket " << descriptor; - free(buffer); - if(descriptor == -1) - return; - close(descriptor); - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has ended (" << descriptor << ")."; - } - - void Socket::setDescriptor(int descriptor) - { - if ((descriptor == -1) && (errno == 24)) - { - shutdown("Too many files open"); - throw coreutils::Exception("Too many files open. Refusing connection."); - } - coreutils::Log(coreutils::LOG_DEBUG_1) << text << " has started (" << descriptor << ")."; - if(descriptor < 3) - throw coreutils::Exception("Descriptor out of range", __FILE__, __LINE__); - this->descriptor = descriptor; - onRegister(); - enableSocket(); - onRegistered(); - } - - int Socket::getDescriptor() - { - return descriptor; - } - - void Socket::setBufferSize(int length) - { - this->length = length; - buffer = (char *)realloc(buffer, length); - } - - int Socket::getBufferSize() - { - return length; - } - - void Socket::onRegister() {} - - void Socket::onRegistered() {} - - void Socket::onUnregister() {} - - void Socket::onUnregistered() {} - - bool Socket::eventReceived(struct epoll_event event, long long eventId) { - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor(); - if(inHandler) - coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true."; - inHandler = true; - if(event.events & EPOLLRDHUP) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP"; - readHangup = true; - shutdown("hangup received"); - } - if(event.events & EPOLLIN) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN"; - coreutils::ZString zbuffer(buffer, length); - // receiveData() calls all the way down through - // onDataReceived()/HTTPRequest::doParse()/application request - // handling with NO exception protection anywhere in that chain - - // confirmed live: a single thrown coreutils::Exception (e.g. a - // malformed request hitting a parse-time throw, well before - // ApiSession::handleComplete()'s own try/catch around - // match.render() ever runs) reached std::terminate() and killed - // the entire server process, not just this one connection, since - // this runs on an EPoll worker thread and C++ terminates the - // whole program on an exception escaping a thread. The old - // lock.lock()/lock.unlock() pair (no RAII) also leaked the lock - // permanently on this same path - std::lock_guard fixes that as - // a side effect of adding the try/catch here. - try { - std::lock_guard guard(lock); - receiveData(zbuffer); - } catch(coreutils::Exception &e) { - // Also to stderr, not just Log() - this process's Log class - // writes through a static listener/file neither this server - // nor its main() ever configures (its own startup banner - // uses plain std::cout, not Log at all), so a Log()-only - // message here would be silently dropped. stderr reliably - // lands in the log file given how this process is started - // (both stdout and stderr redirected to the same file). - std::cerr << "Exception processing socket " << getDescriptor() << ": " << e.text << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Exception processing socket " << getDescriptor() << ": " << e.text; - shutdown("exception during receiveData: " + e.text); - } catch(std::exception &e) { - std::cerr << "std::exception processing socket " << getDescriptor() << ": " << e.what() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "std::exception processing socket " << getDescriptor() << ": " << e.what(); - shutdown(std::string("exception during receiveData: ") + e.what()); - } catch(...) { - std::cerr << "Unknown exception processing socket " << getDescriptor() << std::endl; - coreutils::Log(coreutils::LOG_EXCEPT) << "Unknown exception processing socket " << getDescriptor(); - shutdown("unknown exception during receiveData"); - } - } - if(event.events & EPOLLWRNORM) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM"; - writeSocket(); - } - inHandler = false; - // A graceful shutdown() (session.terminate() -> "goodbye" reply - // queued via write(), then shutdown() called - see e.g. IMAP's - // LOGOUT/POP3's QUIT/SMTP's QUIT) used to make keepAlive false - // the moment shutDown was set, in this SAME event cycle that had - // just queued that reply - Thread::run() then deletes this object - // immediately on a false return, before the reply was ever - // written to the actual socket (write() only queues into fifo; - // the real send() happens in writeSocket(), only reachable via a - // LATER EPOLLWRNORM event this object never lived to receive). - // Confirmed live: LOGOUT/QUIT connections closed correctly but - // their own final reply line never reached the client. Staying - // alive while shutDown is set but fifo still has data - as long - // as the peer hasn't already hung up (readHangup - trying to - // drain to a peer that's already gone is pointless, not graceful) - // - gives that queued reply a real chance to go out before this - // socket actually closes. - bool keepAlive = !shutDown || (fifo.size() > 0 && !readHangup); - - // resetSocket() re-arms this fd via EPOLL_CTL_MOD, which makes it - // immediately eligible for delivery to ANY worker thread's - // epoll_wait() - including one already blocked and about to wake - // up right away. The old code called resetSocket() from inside - // the branches above while several more statements (an - // unconditional inHandler write, this Log call, and `return - // !shutDown`) still touched `this` afterward - a second thread - // could legitimately receive the re-armed event, finish handling - // it, and delete this object (Thread::run() does exactly that - // when eventReceived() returns false) before this thread's own - // remaining statements ran, a heap-use-after-free confirmed via - // AddressSanitizer under concurrent load. resetSocket() must be - // the last thing that ever touches `this`, with the return value - // already captured in a local (`keepAlive`) beforehand. - coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process ending for socket " << getDescriptor(); - if(keepAlive) - resetSocket(); - return keepAlive; - } - - void Socket::connectionRequest() {} - - void Socket::onDataReceived(std::string data) { - throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1); - } - - void Socket::onDataReceived(coreutils::ZString &data) { - onDataReceived(std::string(data.getData(), data.getLength())); - } - - void Socket::receiveData(coreutils::ZString &buffer) { - coreutils::ZString blank(""); - if(buffer.getLength() <= 0) - throw coreutils::Exception("Request to receive data with a zero buffer length.", __FILE__, __LINE__, -1); - int len; - int error = -1; - if((len = ::read(getDescriptor(), buffer.getData(), buffer.getLength())) >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - // coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer; - onDataReceived(zbuffer); - } else { - error = errno; - switch (error) { - - // When a listening socket receives a connection - // request we get one of these. - - case ENOTCONN: - connectionRequest(); - break; - - case ECONNRESET: - break; - - default: - throw coreutils::Exception("Error in read of data from socket.", __FILE__, __LINE__, error); - } - } - } - - int Socket::sendData(const char *data, int length) { - return ::write(descriptor, data, length); - } - - void Socket::writeSocket() { - outlock.lock(); - // coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor(); - if(fifo.size() > 0) { - // No longer gated on !shutDown: writeSocket() only ever runs in - // response to an EPOLLWRNORM event this object explicitly - // re-armed for (see resetSocket()'s own doc comment) - refusing - // to actually perform that queued write once shutDown was set - // was exactly what left a graceful QUIT/LOGOUT's own reply - // stuck in the fifo forever, never reaching the client. - { - // sendData()'s return value was previously discarded and the - // fifo entry popped unconditionally after ONE call - correct - // only by accident, since a single write()/SSL_write() call is - // never guaranteed to accept an entire buffer. A response - // small enough to fit the kernel's socket send buffer in one - // shot (true for almost everything on this site, and always - // true testing over loopback, where that buffer is - // effectively unlimited) went out fine either way - but a - // large one (confirmed with a real 208KB response, mostly an - // embedded base64 font: fine over loopback, but only ~65KB of - // it ever reached a real remote client before the rest was - // silently dropped here) left the client waiting forever for - // bytes matching its own Content-Length that were never - // coming. Now: pop only once the FULL entry has gone out; - // otherwise trim off just what was actually sent and leave - // the remainder queued - resetSocket() (called at the end of - // every eventReceived(), see its own doc comment) keeps - // re-arming EPOLLWRNORM as long as the fifo isn't empty, so - // the next write-ready event retries this same entry - // automatically. A return <= 0 (EAGAIN or a transient error) - // sends nothing this round and is handled identically - just - // leave the entry queued for the next attempt. - int sent = sendData(fifo.front().c_str(), fifo.front().length()); - if(sent > 0) { - if((size_t)sent >= fifo.front().length()) - fifo.pop(); - else - fifo.front().erase(0, sent); - } - } - } - outlock.unlock(); - } - - int Socket::write(std::string data) { - outlock.lock(); - fifo.emplace(data); - outlock.unlock(); - if(lock.try_lock()) { - resetSocket(); - lock.unlock(); - } - return 1; - } - - void Socket::output(std::stringstream &out) - { - out << "|" << descriptor << "|"; - } - - bool Socket::needsToWrite() - { - return fifo.size() > 0; - } - - void Socket::shutdown(std::string text) { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Shutdown requested from " << this->text << " (" << descriptor << ") with reason: " << text << "."; - shutDown = true; - reset = false; - } - - void Socket::enableSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, getDescriptor(), &event); - } - - void Socket::disableSocket() { - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, getDescriptor(), NULL); - } - - void Socket::resetSocket() { - struct epoll_event event; - event.data.ptr = this; - event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET; - if(fifo.size() > 0) - event.events |= EPOLLWRNORM; - // Was `if(!shutDown)` - refused to re-arm at all once shutting - // down, even with data still queued to send (e.g. a graceful - // QUIT/LOGOUT's own final reply - see eventReceived()'s own doc - // comment on keepAlive for the full chain this broke). Re-arming - // for just long enough to drain the fifo, when the peer is still - // there to receive it (!readHangup - draining to an already-gone - // peer is pointless), is what actually lets that reply go out - // before this socket closes. - if(!shutDown || (fifo.size() > 0 && !readHangup)) - epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, getDescriptor(), &event); - } - - -} diff --git a/Socket.h.pre-wantread-fix-20260905 b/Socket.h.pre-wantread-fix-20260905 deleted file mode 100644 index d94d855..0000000 --- a/Socket.h.pre-wantread-fix-20260905 +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef __Socket_h__ -#define __Socket_h__ - -#include "ZString.h" -#include -#include -#include - -namespace core { - - class EPoll; - - /// - /// Socket - /// - /// The core component to managing a socket. - /// - /// Hooks into the EPoll through the registration and unregistration process and provides a - /// communication socket of the specified protocol type. This object provides for all receiving - /// data threading through use of the EPoll object and also provides buffering for output data - /// requests to the socket. - /// - /// A program using a socket object can request to open a socket (network or device) and - /// communicate through the streambuffer interface of the socket object. - /// - /// The socket side of the Socket accepts EPOLLIN event and will maintain the data in a buffer - /// for the stream readers to read. A onDataReceived event is then sent with the data received in - /// the buffer that can be read through the stream. Only sockets that send events to epoll can be - /// used with this object. - /// - /// When writing to the stream the data is written into a buffer and a EPOLLOUT is scheduled. Upon - /// receiving the EPOLLOUT event then the buffer is written to the socket output. - /// - - class Socket { - - public: - - /// - /// Constructor - /// - /// @param ePoll The EPoll socket descriptor. - /// @param text A title for this socket. - /// - - Socket(EPoll &ePoll, std::string text = ""); - - /// - /// Destructor - /// - - virtual ~Socket(); - - /// - /// Use the shutdown() method to terminate the socket connection and remove resources. - /// This method is provided to ensure that all destructors are called for all inherited - /// objects with a virtual destructor. - /// - - void shutdown(std::string text = "unknown"); - - /// - /// setDescriptor establishes the file descriptor for the socket and registers the socket - /// on the EPoll controller. setDescriptor will invoke the onRegister() event. - /// - - void setDescriptor(int descriptor); /// fifo; - - // Counts consecutive writeSocket() calls that sent zero bytes for - // the SAME queued fifo entry (SSL_ERROR_WANT_READ from - // TLS::sendData() is the confirmed real case - see writeSocket()'s - // own doc comment). resetSocket() re-arms EPOLLWRNORM whenever the - // fifo is non-empty regardless of WHY the last send failed, and an - // already-writable TCP socket (the common case) makes EPOLLWRNORM - // fire again essentially immediately - so a send that keeps - // failing for a reason unrelated to socket writability (like SSL - // needing a read first) spins this cycle as fast as the CPU can - // run it, forever, with zero actual progress. Confirmed live: one - // stuck connection generated a 55GB, then a 12.9GB debug log - // before being noticed, at roughly 20 log lines per millisecond. - // Reset to 0 on any successful send (even a partial one - real - // progress happened); past the threshold, the connection is - // clearly never going to un-stick itself and gets shut down. - int consecutiveWriteStalls = 0; - - void resetSocket(); - - std::mutex lock; - - }; - -} - -#endif - diff --git a/Socket.h.pre-writestall-fix-20260903 b/Socket.h.pre-writestall-fix-20260903 deleted file mode 100644 index 3ec7495..0000000 --- a/Socket.h.pre-writestall-fix-20260903 +++ /dev/null @@ -1,221 +0,0 @@ -#ifndef __Socket_h__ -#define __Socket_h__ - -#include "ZString.h" -#include -#include -#include - -namespace core { - - class EPoll; - - /// - /// Socket - /// - /// The core component to managing a socket. - /// - /// Hooks into the EPoll through the registration and unregistration process and provides a - /// communication socket of the specified protocol type. This object provides for all receiving - /// data threading through use of the EPoll object and also provides buffering for output data - /// requests to the socket. - /// - /// A program using a socket object can request to open a socket (network or device) and - /// communicate through the streambuffer interface of the socket object. - /// - /// The socket side of the Socket accepts EPOLLIN event and will maintain the data in a buffer - /// for the stream readers to read. A onDataReceived event is then sent with the data received in - /// the buffer that can be read through the stream. Only sockets that send events to epoll can be - /// used with this object. - /// - /// When writing to the stream the data is written into a buffer and a EPOLLOUT is scheduled. Upon - /// receiving the EPOLLOUT event then the buffer is written to the socket output. - /// - - class Socket { - - public: - - /// - /// Constructor - /// - /// @param ePoll The EPoll socket descriptor. - /// @param text A title for this socket. - /// - - Socket(EPoll &ePoll, std::string text = ""); - - /// - /// Destructor - /// - - virtual ~Socket(); - - /// - /// Use the shutdown() method to terminate the socket connection and remove resources. - /// This method is provided to ensure that all destructors are called for all inherited - /// objects with a virtual destructor. - /// - - void shutdown(std::string text = "unknown"); - - /// - /// setDescriptor establishes the file descriptor for the socket and registers the socket - /// on the EPoll controller. setDescriptor will invoke the onRegister() event. - /// - - void setDescriptor(int descriptor); /// fifo; - - void resetSocket(); - - std::mutex lock; - - }; - -} - -#endif - diff --git a/TCPServer.cpp.pre-tlsaccept-fix-20260831 b/TCPServer.cpp.pre-tlsaccept-fix-20260831 deleted file mode 100644 index 2df7fdb..0000000 --- a/TCPServer.cpp.pre-tlsaccept-fix-20260831 +++ /dev/null @@ -1,131 +0,0 @@ -#include "TCPServer.h" -#include "EPoll.h" -#include "Exception.h" -#include "Log.h" -#include "TCPSession.h" - -namespace core { - - TCPServer::TCPServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo, std::string delimiter, int depth, std::string text) - : TCPSocket(ePoll, tlsInfo, text), commands(delimiter, depth) { - - setDescriptor(socket(AF_INET, SOCK_STREAM, 0)); - int yes = 1; - setsockopt(getDescriptor(), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); - - if (bind(getDescriptor(), address.getPointer(), address.addressLength) < 0) - throw coreutils::Exception("Error on bind to socket: " + std::to_string(errno)); - - if (listen(getDescriptor(), 20) < 0) - throw coreutils::Exception("Error on listen to socket"); - } - - TCPServer::~TCPServer() { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Closing server socket " << getDescriptor() << "."; - close(getDescriptor()); - } - - void TCPServer::connectionRequest() { - coreutils::Log(coreutils::LOG_DEBUG_2) << "Connection request is being received on socket " << getDescriptor() << "."; - lock.lock(); - TCPSession *session = accept(); - if (session) - sessions.push_back(session); - if (tlsInfo) { - registerSocket(session->getDescriptor()); - acceptSocket(); - } - lock.unlock(); - } - - TCPSession * TCPServer::accept() { - - try { - - TCPSession *session = getSocketAccept(ePoll); - session->setDescriptor(::accept(getDescriptor(), (struct sockaddr *)&session->ipAddress.addr, &session->ipAddress.addressLength)); - // if(blackList && blackList->contains(session->ipAddress.getClientAddress())) { - // session->shutdown(); - // Log(LOG_WARN) << "Client at IP address " << session->ipAddress.getClientAddress() << " is blacklisted and was denied a connection."; - // return NULL; - // } - // if(whiteList && !whiteList->contains(session->ipAddress.getClientAddress())) { - // session->shutdown(); - // Log(LOG_WARN) << "Client at IP address " << session->ipAddress.getClientAddress() << " is not authorized and was denied a connection."; - // return NULL; - // } - return session; - } - catch (coreutils::Exception e) { - coreutils::Log(coreutils::LOG_EXCEPT) << "Major error on session initialization. Error is '" << e.text << "'."; - } - catch (...) { - coreutils::Log(coreutils::LOG_EXCEPT) << "Unnspecified error on session initialization."; - } - return NULL; - } - - void TCPServer::removeFromSessionList(TCPSession *session) { - std::vector::iterator cursor; - lock.lock(); - for(cursor = sessions.begin(); cursor < sessions.end(); ++cursor) - if(*cursor == session) { - sessions.erase(cursor); - break; - } - lock.unlock(); - } - - void TCPServer::sessionErrorHandler(std::string errorString, std::stringstream &out) { - throw coreutils::Exception(errorString); - } - - TCPSession * TCPServer::getSocketAccept(EPoll &ePoll) { - return new TCPSession(ePoll, this); - } - - void TCPServer::output(std::stringstream &out) { - out << "Use the 'help' command to list the commands for this server." << std::endl; - } - - int TCPServer::processCommand(coreutils::ZString &request, TCPSession &session) { - int sequence = 0; - for (auto *sessionx : sessions) - { - session.out << "|" << ++sequence; - sessionx->output(session.out); - session.out << "|" << std::endl; - } - return 1; - } - - void TCPServer::sendToAll(std::stringstream &data) { - for (auto session : sessions) - session->write(data.str()); - data.str(""); - } - - void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender) { - for (auto session : sessions) - if (session != &sender) - session->write(data.str()); - data.str(""); - } - - void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender, SessionFilter filter) { - for (auto session : sessions) - if (filter.test(*session)) - if (session != &sender) - session->write(data.str()); - data.str(""); - } - - TCPSession *TCPServer::getSessionByAlias(void *alias) { - coreutils::Log(coreutils::LOG_DEBUG_1) << alias; - for (auto session : sessions) - if (session->compareAlias(alias)) - return session; - return NULL; - } - -} diff --git a/TCPServer.h.pre-tlsaccept-fix-20260831 b/TCPServer.h.pre-tlsaccept-fix-20260831 deleted file mode 100644 index 5017ef6..0000000 --- a/TCPServer.h.pre-tlsaccept-fix-20260831 +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef __TCPServer_h__ -# define __TCPServer_h__ - -# include "Command.h" -# include "CommandList.h" -# include "IPAddressList.h" -# include "Socket.h" -# include "SubscriptionManager.h" -# include "TCPSession.h" -# include "TCPSocket.h" -# include "TLSInfo.h" - -namespace core { - - /// - /// TCPServer - /// - /// Manage a socket connection as a TCP server type. Connections to the socket are processed through - /// the accept functionality. - /// - /// A list of connections is maintained in a vector object. - /// - /// This object extends the Command object as well so it can be added to a Console object and - /// process commands to display status information. - /// - - class TCPServer : public TCPSocket, public Command { - - public: - - /// - /// The constructor for the TCPServer object. - /// - /// @param ePoll the EPoll instance that manages the socket. - /// @param url the IP address for the socket to receive connection requests. - /// @param port the port number that the socket will listen on. - /// @param commandName the name of the command used to invoke the status display for this object. - /// - - TCPServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo, std::string delimiter = " ", int depth = 10, std::string text = ""); - - /// - /// The destructor for this object. - /// - - virtual ~TCPServer(); - - virtual void sessionErrorHandler(std::string errorString, std::stringstream &out); - - /// - /// getSocketAccept is designed to allow a polymorphic extension of this object to - /// return a type of object that extends the definition of the server socket. - /// Returning the appropriate session object that extends from Session provides - /// the mechanism where the server can select the protocol dialog for the desired - /// service. - /// - - virtual TCPSession *getSocketAccept(EPoll &epoll); - - /// - /// The list of sessions that are currently open and being maintained by this object. - /// - - std::vector sessions; - - /// - /// The commands object is a CommandList and is used to store Command objects to be - /// parsed and run as data comes into the session. - /// - - CommandList commands; - - /// - /// If not NULL the blacklist object can be assigned to this server socket and the server - /// IP addresses connecting to the server attempting to accept a socket are contained in - /// this list then the connection is rejected and no accept is granted. - /// - - IPAddressList *blackList; - - /// - /// If not NULL the blacklist object can be assigned to this server socket and the server - /// IP addresses connecting to the server attempting to accept a socket are contained in - /// this list then the connection is rejected and no accept is granted. - /// - - IPAddressList *whiteList; - - void removeFromSessionList(TCPSession *session); - - void output(std::stringstream &out); ///< Output the consoles array to the console. - - /// - /// - /// - - void sendToAll(std::stringstream &out); - - /// - /// Use this sendToAll method to output the contents of the out stream - /// to all the connections on the server excluding the sender session. - /// - - void sendToAll(std::stringstream &out, TCPSession &sender, SessionFilter filter); - - /// - /// Use this sendToAll method to output the contents of the out stream - /// to all the connections on the server excluding the sender session - /// and the entries identified by the passed in filter object. - /// - - void sendToAll(std::stringstream &out, TCPSession &sender); - - /// - /// The Subscription Manager tracks all subscriptions on the server. - /// - - SubscriptionManager subscriptions; - - /// - /// Use the getSessionByAlias to retrieve a session pointer by the value - /// of the alias pointer. - /// - - TCPSession *getSessionByAlias(void *alias); - - protected: - /// - /// This method is called by the lower socket when a connection request comes into - /// the listening and bound server port. - /// - - void connectionRequest() override; - - /// - /// This method is called when the Command associated with this object is requested - /// because a user has typed in the associated command name on a command entry line. - /// - /// @param the session object to write the output to. - /// - - int processCommand(coreutils::ZString &request, TCPSession &session) override; - - private: - TCPSession *accept(); - std::mutex lock; - }; - -} - -#endif diff --git a/TCPSocket.cpp.pre-iolock-fix-20260831 b/TCPSocket.cpp.pre-iolock-fix-20260831 deleted file mode 100644 index bf6adcc..0000000 --- a/TCPSocket.cpp.pre-iolock-fix-20260831 +++ /dev/null @@ -1,109 +0,0 @@ -#include "TCPSocket.h" -#include "EPoll.h" -#include "Log.h" -#include "Exception.h" -#include "errno.h" - -namespace core { - - TCPSocket::TCPSocket(EPoll &ePoll) : Socket(ePoll) {} - - TCPSocket::TCPSocket(EPoll &ePoll, std::string text) : Socket(ePoll, text) {} - - TCPSocket::TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text) : Socket(ePoll, text), TLS(tlsInfo), tlsInfo(tlsInfo) {} - - TCPSocket::TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text) : Socket(ePoll, text), TLS(ctx) {} - - TCPSocket::~TCPSocket() {} - - void TCPSocket::connect(IPAddress &address) { - setDescriptor(socket(AF_INET, SOCK_STREAM, 0)); - if(::connect(getDescriptor(), (struct sockaddr *)&address.addr, address.addressLength) == -1) - throw coreutils::Exception("Error on connect to TCP socket." + errno); - coreutils::Log(coreutils::LOG_DEBUG_3) << "Connected to IP..." << address.getClientAddressAndPort(); - } - - void TCPSocket::output(std::stringstream &out) { - out << "|" << ipAddress.getClientAddressAndPort(); - } - - void TCPSocket::receiveData(coreutils::ZString &buffer) { - // Dispatch on `ctx`, not `tlsInfo` - the SSL_CTX*-taking constructor - // (used for every accepted TCPSession) never sets `tlsInfo` at all, - // leaving it as uninitialized garbage that intermittently (whenever - // that garbage happened to be non-zero) routed a plain, non-TLS - // session's data through SSL_read()/SSL_write() against an - // uninitialized SSL*, segfaulting inside libssl. `ctx` is the - // reliable signal: real and non-NULL for an actual TLS server/ - // session (set in TLS::createContext(), inherited by sessions via - // TLS(SSL_CTX*)), safely NULL by default otherwise (see TLS.h). - if(ctx) { - int len = TLS::receiveData(buffer); - if(len >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - onDataReceived(zbuffer); - } - } else { - Socket::receiveData(buffer); - } - } - - int TCPSocket::sendData(const char *data, int length) { - if(ctx) - return TLS::sendData(data, length); - else - return Socket::sendData(data, length); - } - - void TCPSocket::performTLSHandshake() { - registerSocket(getDescriptor()); - acceptSocket(); - } - - void TCPSocket::onDataReceived(coreutils::ZString &data) { - - if (data.getLength() > 0) { - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize + data.getLength()); - memcpy(lineBuffer + lineBufferSize, data.getData(), data.getLength()); - lineBufferSize += data.getLength(); - while (lineBufferSize > 0) { - if (blockSize == 0) { - lineLength = strcspn(lineBuffer, "\r\n"); - if (lineLength == lineBufferSize) - break; - coreutils::ZString zLine(lineBuffer, lineLength); - onLineReceived(zLine); - if (lineBuffer[lineLength] == '\r') - ++lineLength; - if (lineBuffer[lineLength] == '\n') - ++lineLength; - lineBufferSize -= lineLength; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + lineLength, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - else if (lineBufferSize >= blockLength) { - coreutils::ZString zBlock(lineBuffer, blockLength); - onBlockReceived(zBlock); - lineBufferSize -= blockLength; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + blockLength, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - } - } - } - - void TCPSocket::onLineReceived(coreutils::ZString &line) { - if (term) - shutdown("termination requested"); - } - - void TCPSocket::onBlockReceived(coreutils::ZString &block) {} - - void TCPSocket::setBlockSize(int blockSize) { - this->blockSize = blockSize; - } - -} - diff --git a/TCPSocket.cpp.pre-tlsaccept-fix-20260831 b/TCPSocket.cpp.pre-tlsaccept-fix-20260831 deleted file mode 100644 index 1d7b555..0000000 --- a/TCPSocket.cpp.pre-tlsaccept-fix-20260831 +++ /dev/null @@ -1,104 +0,0 @@ -#include "TCPSocket.h" -#include "EPoll.h" -#include "Log.h" -#include "Exception.h" -#include "errno.h" - -namespace core { - - TCPSocket::TCPSocket(EPoll &ePoll) : Socket(ePoll) {} - - TCPSocket::TCPSocket(EPoll &ePoll, std::string text) : Socket(ePoll, text) {} - - TCPSocket::TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text) : Socket(ePoll, text), TLS(tlsInfo), tlsInfo(tlsInfo) {} - - TCPSocket::TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text) : Socket(ePoll, text), TLS(ctx) {} - - TCPSocket::~TCPSocket() {} - - void TCPSocket::connect(IPAddress &address) { - setDescriptor(socket(AF_INET, SOCK_STREAM, 0)); - if(::connect(getDescriptor(), (struct sockaddr *)&address.addr, address.addressLength) == -1) - throw coreutils::Exception("Error on connect to TCP socket." + errno); - coreutils::Log(coreutils::LOG_DEBUG_3) << "Connected to IP..." << address.getClientAddressAndPort(); - } - - void TCPSocket::output(std::stringstream &out) { - out << "|" << ipAddress.getClientAddressAndPort(); - } - - void TCPSocket::receiveData(coreutils::ZString &buffer) { - // Dispatch on `ctx`, not `tlsInfo` - the SSL_CTX*-taking constructor - // (used for every accepted TCPSession) never sets `tlsInfo` at all, - // leaving it as uninitialized garbage that intermittently (whenever - // that garbage happened to be non-zero) routed a plain, non-TLS - // session's data through SSL_read()/SSL_write() against an - // uninitialized SSL*, segfaulting inside libssl. `ctx` is the - // reliable signal: real and non-NULL for an actual TLS server/ - // session (set in TLS::createContext(), inherited by sessions via - // TLS(SSL_CTX*)), safely NULL by default otherwise (see TLS.h). - if(ctx) { - int len = TLS::receiveData(buffer); - if(len >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - onDataReceived(zbuffer); - } - } else { - Socket::receiveData(buffer); - } - } - - int TCPSocket::sendData(const char *data, int length) { - if(ctx) - return TLS::sendData(data, length); - else - return Socket::sendData(data, length); - } - - void TCPSocket::onDataReceived(coreutils::ZString &data) { - - if (data.getLength() > 0) { - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize + data.getLength()); - memcpy(lineBuffer + lineBufferSize, data.getData(), data.getLength()); - lineBufferSize += data.getLength(); - while (lineBufferSize > 0) { - if (blockSize == 0) { - lineLength = strcspn(lineBuffer, "\r\n"); - if (lineLength == lineBufferSize) - break; - coreutils::ZString zLine(lineBuffer, lineLength); - onLineReceived(zLine); - if (lineBuffer[lineLength] == '\r') - ++lineLength; - if (lineBuffer[lineLength] == '\n') - ++lineLength; - lineBufferSize -= lineLength; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + lineLength, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - else if (lineBufferSize >= blockLength) { - coreutils::ZString zBlock(lineBuffer, blockLength); - onBlockReceived(zBlock); - lineBufferSize -= blockLength; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + blockLength, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - } - } - } - - void TCPSocket::onLineReceived(coreutils::ZString &line) { - if (term) - shutdown("termination requested"); - } - - void TCPSocket::onBlockReceived(coreutils::ZString &block) {} - - void TCPSocket::setBlockSize(int blockSize) { - this->blockSize = blockSize; - } - -} - diff --git a/TCPSocket.cpp.pre-wantread-fix-20260905 b/TCPSocket.cpp.pre-wantread-fix-20260905 deleted file mode 100644 index 4cdf747..0000000 --- a/TCPSocket.cpp.pre-wantread-fix-20260905 +++ /dev/null @@ -1,185 +0,0 @@ -#include "TCPSocket.h" -#include "EPoll.h" -#include "Log.h" -#include "Exception.h" -#include "errno.h" -#include -#include - -namespace core { - - TCPSocket::TCPSocket(EPoll &ePoll) : Socket(ePoll) {} - - TCPSocket::TCPSocket(EPoll &ePoll, std::string text) : Socket(ePoll, text) {} - - TCPSocket::TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text) : Socket(ePoll, text), TLS(tlsInfo), tlsInfo(tlsInfo) {} - - TCPSocket::TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text) : Socket(ePoll, text), TLS(ctx) {} - - TCPSocket::~TCPSocket() {} - - void TCPSocket::connect(IPAddress &address) { - setDescriptor(socket(AF_INET, SOCK_STREAM, 0)); - if(::connect(getDescriptor(), (struct sockaddr *)&address.addr, address.addressLength) == -1) - throw coreutils::Exception("Error on connect to TCP socket." + errno); - coreutils::Log(coreutils::LOG_DEBUG_3) << "Connected to IP..." << address.getClientAddressAndPort(); - } - - void TCPSocket::output(std::stringstream &out) { - out << "|" << ipAddress.getClientAddressAndPort(); - } - - void TCPSocket::receiveData(coreutils::ZString &buffer) { - // Dispatch on `ctx`, not `tlsInfo` - the SSL_CTX*-taking constructor - // (used for every accepted TCPSession) never sets `tlsInfo` at all, - // leaving it as uninitialized garbage that intermittently (whenever - // that garbage happened to be non-zero) routed a plain, non-TLS - // session's data through SSL_read()/SSL_write() against an - // uninitialized SSL*, segfaulting inside libssl. `ctx` is the - // reliable signal: real and non-NULL for an actual TLS server/ - // session (set in TLS::createContext(), inherited by sessions via - // TLS(SSL_CTX*)), safely NULL by default otherwise (see TLS.h). - if(ctx) { - int len; - { - // Held only around the raw SSL_read call - released before - // onDataReceived() so the request-handling chain it triggers - // (which reaches Socket::write()/outlock) never runs while - // ioLock is held. See ioLock's own doc comment in TCPSocket.h. - std::lock_guard ioGuard(ioLock); - len = TLS::receiveData(buffer); - } - if(len >= 0) { - coreutils::ZString zbuffer(buffer.getData(), len); - onDataReceived(zbuffer); - } - } else { - Socket::receiveData(buffer); - } - } - - int TCPSocket::sendData(const char *data, int length) { - if(ctx) { - std::lock_guard ioGuard(ioLock); - return TLS::sendData(data, length); - } else - return Socket::sendData(data, length); - } - - void TCPSocket::performTLSHandshake() { - // registerSocket() makes this fd eligible for epoll dispatch to ANY - // worker thread immediately. ioLock MUST be acquired BEFORE that - // call, not after - a first attempt here took the lock AFTER - // registerSocket(), which still left a real window: another thread - // could win the race for ioLock (via a read/write event on this - // same brand-new connection) and have SSL_read/SSL_write silently - // complete the handshake on OpenSSL's own initiative, and only - // THEN would this thread acquire ioLock and run its own, now - // redundant, acceptSocket() (SSL_accept) against an already- - // negotiated SSL object - confirmed via gdb `thread apply all bt` - // during a live crash: one thread mid-SSL_read, a second thread - // simultaneously inside performTLSHandshake, both on the identical - // object address. Acquiring ioLock first closes that window - // entirely: no other thread can even begin touching this - // connection's SSL object until this thread's own handshake - // attempt has released the lock. acceptSocket() only attempts - // SSL_accept() once and may leave the handshake incomplete - // (WANT_READ/WANT_WRITE) - that's fine, OpenSSL transparently - // finishes a pending handshake on the next SSL_read/SSL_write, - // which ioLock already serializes against everything else. - std::lock_guard ioGuard(ioLock); - registerSocket(getDescriptor()); - acceptSocket(); - } - - void TCPSocket::onDataReceived(coreutils::ZString &data) { - - if (data.getLength() > 0) { - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize + data.getLength()); - memcpy(lineBuffer + lineBufferSize, data.getData(), data.getLength()); - lineBufferSize += data.getLength(); - while (lineBufferSize > 0) { - if (blockSize == 0) { - // strcspn() treats its argument as a NUL-terminated C - // string and keeps scanning past the end looking for one - - // lineBuffer is a raw byte buffer sized via realloc() to - // exactly lineBufferSize bytes, with no NUL terminator - // appended anywhere. Whenever the buffered data doesn't yet - // contain a CRLF (an ordinary, frequent case - a line split - // across TCP reads, or mid-way through a long one, e.g. a - // large email body), strcspn() read straight past - // lineBufferSize into whatever heap memory happened to - // follow, hunting for a byte that happened to match '\r', - // '\n', or an accidental '\0' - producing a lineLength - // larger than the buffer actually holds. Confirmed live via - // gdb: SIGSEGV inside a LATER, unrelated realloc() call on - // this same lineBuffer, well downstream of where the real - // corruption happened (lineBufferSize -= lineLength - // underflows when lineLength > lineBufferSize, and that - // negative value gets handed to realloc() as a size_t - - // realloc(ptr, SIZE_MAX-ish) - during a real inbound SMTP - // message with a long quoted-printable line). memchr() is - // the bounded equivalent - it never looks past the length - // given, so "no CRLF yet" correctly comes back as "not - // found" instead of reading off the end of the allocation. - char *cr = (char *)memchr(lineBuffer, '\r', lineBufferSize); - char *nl = (char *)memchr(lineBuffer, '\n', lineBufferSize); - char *found = (cr && nl) ? std::min(cr, nl) : (cr ? cr : nl); - lineLength = found ? (int)(found - lineBuffer) : lineBufferSize; - if (lineLength == lineBufferSize) - break; - coreutils::ZString zLine(lineBuffer, lineLength); - onLineReceived(zLine); - // lineLength < lineBufferSize is guaranteed by the break - // above, so this first index is always in bounds; bump - // past a lone '\r' only if there's still at least one more - // buffered byte to safely check for a following '\n' - - // otherwise (a bare trailing '\r' with nothing buffered - // after it yet) leave it for the next read to resolve, - // rather than reading lineBuffer[lineBufferSize], one past - // the allocation. - if (lineBuffer[lineLength] == '\r') - ++lineLength; - if (lineLength < lineBufferSize && lineBuffer[lineLength] == '\n') - ++lineLength; - lineBufferSize -= lineLength; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + lineLength, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - else if (lineBufferSize >= blockSize) { - coreutils::ZString zBlock(lineBuffer, blockSize); - onBlockReceived(zBlock); - lineBufferSize -= blockSize; - if (lineBufferSize > 0) - memmove(lineBuffer, lineBuffer + blockSize, lineBufferSize); - lineBuffer = (char *)realloc(lineBuffer, lineBufferSize); - } - else - // Not enough buffered data yet for a full block - stop and - // wait for the next read, exactly like the line-mode - // branch's own "no complete line yet" break above. - // Without this, a block spanning more than one TCP read - // (any IMAP APPEND literal bigger than a single read()'s - // worth of socket buffer) sends this EPoll worker thread - // into an unconditional busy-spin - lineBufferSize never - // changes, so the while(lineBufferSize > 0) loop above - // never yields back to let more data arrive. - break; - } - } - } - - void TCPSocket::onLineReceived(coreutils::ZString &line) { - if (term) - shutdown("termination requested"); - } - - void TCPSocket::onBlockReceived(coreutils::ZString &block) {} - - void TCPSocket::setBlockSize(int blockSize) { - this->blockSize = blockSize; - } - -} - diff --git a/TCPSocket.h.pre-iolock-fix-20260831 b/TCPSocket.h.pre-iolock-fix-20260831 deleted file mode 100644 index 6dc422a..0000000 --- a/TCPSocket.h.pre-iolock-fix-20260831 +++ /dev/null @@ -1,159 +0,0 @@ - -#ifndef __TCPSocket_h__ -#define __TCPSocket_h__ - -#include "Socket.h" -#include "TLS.h" -#include "TLSInfo.h" -#include "IPAddress.h" - -namespace core { - - /// - /// TCPSocket - /// - /// Provides a network TCP socket. - /// - /// For accessing TCP network functions use this object. The connection oriented nature of TCP - /// provides a single client persistent connection with data error correction and a durable - /// synchronous data connection. - /// - - class TCPSocket : public Socket, public TLS { - - public: - - /// - /// - /// - - TCPSocket(EPoll &ePoll); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text); - - /// - /// - /// - - virtual ~TCPSocket(); - - /// - /// - /// - - void connect(IPAddress &address); - - IPAddress ipAddress; - - /// - /// The output method is called by a socket session (TCPSession) and - /// will output the detail information for the client socket. When extending - /// TCPSocket or TCPSession you can override the method to add attributes - /// to the list. - /// - - virtual void output(std::stringstream &out); - - /// - /// Override this method to receive data directly from the socket as data is - /// received. If you need data split by line termination characters then - /// override the onLineReceived method instead. - /// - - virtual void onDataReceived(coreutils::ZString &data) override; - - /// - /// Override the onLineReceived method to receive a string of characters that - /// represents a single line of data terminated by a LF or CRLF. If onDataReceived - /// was overriden this method will not be called unless the onDataReceived calls - /// this method explicitly using the class and member name. - /// - - virtual void onLineReceived(coreutils::ZString &line); - - /// - /// Override the onBlockReceived method to receive a string of characters that - /// represents a single block of data of length determined by the block length value. If - /// onDataReceived was overriden this method will not be called unless the onDataReceived - /// calls this method explicitly using the class and member name. - /// - - virtual void onBlockReceived(coreutils::ZString &block); - - /// - /// Use setBlockSize to set the amount of data that should be read at once from the - /// session data buffer. - /// If this value is set to 0 then the data will be retrieved - /// - - void setBlockSize(int size = 0); - - /// - /// Dispatches to TLS::receiveData (SSL_read) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::receiveData (plain ::read). Needed - /// because Socket and TLS both declare receiveData as unrelated sibling methods - - /// Socket::receiveData is virtual but TLS::receiveData does not override it, so without - /// this explicit override TLS::receiveData would never be reached and encrypted bytes - /// would be handed to onDataReceived as if they were plaintext. - /// - - virtual void receiveData(coreutils::ZString &buffer) override; - - /// - /// Dispatches to TLS::sendData (SSL_write) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::sendData (plain ::write). Mirrors - /// the receiveData override above for the write path. - /// - - virtual int sendData(const char *data, int length) override; - - /// - /// Completes the TLS handshake (SSL_new/SSL_set_fd/SSL_accept) for - /// THIS socket's own ctx/descriptor. registerSocket()/acceptSocket() - /// are protected TLS members, reachable here via an ordinary - /// same-class call - the point of exposing this as a public wrapper - /// is so a sibling class (TCPServer, which does not inherit - /// TCPSocket-the-session's-particular-instance and so cannot reach - /// another object's protected members directly) can still trigger - /// the handshake on a specific accepted session from - /// TCPServer::connectionRequest(), rather than accidentally running - /// it against the listening server socket itself (see that call - /// site's own comment for the real, confirmed crash this replaces - - /// SSL_read() on an accepted session's never-initialized ssl - /// member). - /// - - void performTLSHandshake(); - - protected: - bool term = false; - TLSInfo *tlsInfo; - - private: - char *lineBuffer = NULL; - int lineBufferSize = 0; - int lineLength = 0; - int blockLength = 0; - int blockSize = 0; - - }; - -} - -#endif diff --git a/TCPSocket.h.pre-tlsaccept-fix-20260831 b/TCPSocket.h.pre-tlsaccept-fix-20260831 deleted file mode 100644 index 67c8ec5..0000000 --- a/TCPSocket.h.pre-tlsaccept-fix-20260831 +++ /dev/null @@ -1,141 +0,0 @@ - -#ifndef __TCPSocket_h__ -#define __TCPSocket_h__ - -#include "Socket.h" -#include "TLS.h" -#include "TLSInfo.h" -#include "IPAddress.h" - -namespace core { - - /// - /// TCPSocket - /// - /// Provides a network TCP socket. - /// - /// For accessing TCP network functions use this object. The connection oriented nature of TCP - /// provides a single client persistent connection with data error correction and a durable - /// synchronous data connection. - /// - - class TCPSocket : public Socket, public TLS { - - public: - - /// - /// - /// - - TCPSocket(EPoll &ePoll); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text); - - /// - /// - /// - - virtual ~TCPSocket(); - - /// - /// - /// - - void connect(IPAddress &address); - - IPAddress ipAddress; - - /// - /// The output method is called by a socket session (TCPSession) and - /// will output the detail information for the client socket. When extending - /// TCPSocket or TCPSession you can override the method to add attributes - /// to the list. - /// - - virtual void output(std::stringstream &out); - - /// - /// Override this method to receive data directly from the socket as data is - /// received. If you need data split by line termination characters then - /// override the onLineReceived method instead. - /// - - virtual void onDataReceived(coreutils::ZString &data) override; - - /// - /// Override the onLineReceived method to receive a string of characters that - /// represents a single line of data terminated by a LF or CRLF. If onDataReceived - /// was overriden this method will not be called unless the onDataReceived calls - /// this method explicitly using the class and member name. - /// - - virtual void onLineReceived(coreutils::ZString &line); - - /// - /// Override the onBlockReceived method to receive a string of characters that - /// represents a single block of data of length determined by the block length value. If - /// onDataReceived was overriden this method will not be called unless the onDataReceived - /// calls this method explicitly using the class and member name. - /// - - virtual void onBlockReceived(coreutils::ZString &block); - - /// - /// Use setBlockSize to set the amount of data that should be read at once from the - /// session data buffer. - /// If this value is set to 0 then the data will be retrieved - /// - - void setBlockSize(int size = 0); - - /// - /// Dispatches to TLS::receiveData (SSL_read) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::receiveData (plain ::read). Needed - /// because Socket and TLS both declare receiveData as unrelated sibling methods - - /// Socket::receiveData is virtual but TLS::receiveData does not override it, so without - /// this explicit override TLS::receiveData would never be reached and encrypted bytes - /// would be handed to onDataReceived as if they were plaintext. - /// - - virtual void receiveData(coreutils::ZString &buffer) override; - - /// - /// Dispatches to TLS::sendData (SSL_write) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::sendData (plain ::write). Mirrors - /// the receiveData override above for the write path. - /// - - virtual int sendData(const char *data, int length) override; - - protected: - bool term = false; - TLSInfo *tlsInfo; - - private: - char *lineBuffer = NULL; - int lineBufferSize = 0; - int lineLength = 0; - int blockLength = 0; - int blockSize = 0; - - }; - -} - -#endif diff --git a/TCPSocket.h.pre-wantread-fix-20260905 b/TCPSocket.h.pre-wantread-fix-20260905 deleted file mode 100644 index f2150a3..0000000 --- a/TCPSocket.h.pre-wantread-fix-20260905 +++ /dev/null @@ -1,178 +0,0 @@ - -#ifndef __TCPSocket_h__ -#define __TCPSocket_h__ - -#include "Socket.h" -#include "TLS.h" -#include "TLSInfo.h" -#include "IPAddress.h" - -namespace core { - - /// - /// TCPSocket - /// - /// Provides a network TCP socket. - /// - /// For accessing TCP network functions use this object. The connection oriented nature of TCP - /// provides a single client persistent connection with data error correction and a durable - /// synchronous data connection. - /// - - class TCPSocket : public Socket, public TLS { - - public: - - /// - /// - /// - - TCPSocket(EPoll &ePoll); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text); - - /// - /// - /// - - TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text); - - /// - /// - /// - - virtual ~TCPSocket(); - - /// - /// - /// - - void connect(IPAddress &address); - - IPAddress ipAddress; - - /// - /// The output method is called by a socket session (TCPSession) and - /// will output the detail information for the client socket. When extending - /// TCPSocket or TCPSession you can override the method to add attributes - /// to the list. - /// - - virtual void output(std::stringstream &out); - - /// - /// Override this method to receive data directly from the socket as data is - /// received. If you need data split by line termination characters then - /// override the onLineReceived method instead. - /// - - virtual void onDataReceived(coreutils::ZString &data) override; - - /// - /// Override the onLineReceived method to receive a string of characters that - /// represents a single line of data terminated by a LF or CRLF. If onDataReceived - /// was overriden this method will not be called unless the onDataReceived calls - /// this method explicitly using the class and member name. - /// - - virtual void onLineReceived(coreutils::ZString &line); - - /// - /// Override the onBlockReceived method to receive a string of characters that - /// represents a single block of data of length determined by the block length value. If - /// onDataReceived was overriden this method will not be called unless the onDataReceived - /// calls this method explicitly using the class and member name. - /// - - virtual void onBlockReceived(coreutils::ZString &block); - - /// - /// Use setBlockSize to set the amount of data that should be read at once from the - /// session data buffer. - /// If this value is set to 0 then the data will be retrieved - /// - - void setBlockSize(int size = 0); - - /// - /// Dispatches to TLS::receiveData (SSL_read) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::receiveData (plain ::read). Needed - /// because Socket and TLS both declare receiveData as unrelated sibling methods - - /// Socket::receiveData is virtual but TLS::receiveData does not override it, so without - /// this explicit override TLS::receiveData would never be reached and encrypted bytes - /// would be handed to onDataReceived as if they were plaintext. - /// - - virtual void receiveData(coreutils::ZString &buffer) override; - - /// - /// Dispatches to TLS::sendData (SSL_write) when this socket was constructed with a - /// TLSInfo/SSL_CTX, otherwise falls back to Socket::sendData (plain ::write). Mirrors - /// the receiveData override above for the write path. - /// - - virtual int sendData(const char *data, int length) override; - - /// - /// Completes the TLS handshake (SSL_new/SSL_set_fd/SSL_accept) for - /// THIS socket's own ctx/descriptor. registerSocket()/acceptSocket() - /// are protected TLS members, reachable here via an ordinary - /// same-class call - the point of exposing this as a public wrapper - /// is so a sibling class (TCPServer, which does not inherit - /// TCPSocket-the-session's-particular-instance and so cannot reach - /// another object's protected members directly) can still trigger - /// the handshake on a specific accepted session from - /// TCPServer::connectionRequest(), rather than accidentally running - /// it against the listening server socket itself (see that call - /// site's own comment for the real, confirmed crash this replaces - - /// SSL_read() on an accepted session's never-initialized ssl - /// member). - /// - - void performTLSHandshake(); - - protected: - bool term = false; - TLSInfo *tlsInfo; - - private: - char *lineBuffer = NULL; - int lineBufferSize = 0; - int lineLength = 0; - int blockSize = 0; - - // Socket's own `lock` (guards receiveData) and `outlock` (guards - // writeSocket/the fifo) are two DIFFERENT mutexes - a thread - // handling EPOLLIN and a different thread handling EPOLLWRNORM for - // the SAME connection can therefore call SSL_read and SSL_write on - // the SAME `ssl*` concurrently. OpenSSL does not support that: an - // SSL object must not be used by more than one thread at a time - // (even in 1.1.0+/3.x, which dropped the old app-supplied - // CRYPTO_set_locking_callback global-locking API entirely - that - // API never protected concurrent use of one SSL object anyway, - // only OpenSSL's own internal global state). Confirmed live via - // dmesg: "traps: apiserver[...] general protection fault ... in - // libssl.so.3" during ordinary interactive use. ioLock serializes - // just the raw SSL_read/SSL_write calls themselves (see - // receiveData/sendData below) - deliberately NOT the surrounding - // request-handling, which already nests `lock` before `outlock` - // (receiveData's call chain reaches Socket::write(), which takes - // outlock) - wrapping any larger scope in ioLock+lock+outlock - // together risks recreating that same ordering as a new cycle. - std::mutex ioLock; - - }; - -} - -#endif diff --git a/TLS.cpp.pre-optional-mtls-20260831 b/TLS.cpp.pre-optional-mtls-20260831 deleted file mode 100644 index d370d8c..0000000 --- a/TLS.cpp.pre-optional-mtls-20260831 +++ /dev/null @@ -1,201 +0,0 @@ -#include "TLS.h" -#include "Exception.h" -#include "Log.h" -#include -#include - -namespace core { - - static pthread_mutex_t *lockarray; - - static unsigned long thread_id(void) { - return ((unsigned long) pthread_self()); - } - - static void lock_callback(int mode, int type, const char *file, int line) { - if(mode & CRYPTO_LOCK) - pthread_mutex_lock(&(lockarray[type])); - else - pthread_mutex_unlock(&(lockarray[type])); - } - - static int generate_session_id(const SSL *ssl, unsigned char *id, unsigned int *id_len) { - char *session_id_prefix = (char *)"BARANT"; - unsigned int count = 0; - coreutils::Log(coreutils::LOG_DEBUG_3) << "Generating unique session id."; - do { - RAND_bytes(id, *id_len); - memcpy(id, session_id_prefix, (strlen(session_id_prefix) < *id_len)); - } while(SSL_has_matching_session_id(ssl, id, *id_len) && (++count < 10)); - return 1; - } - - void handshake_complete(const SSL *ssl, int where, int ret) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "==>" << SSL_state_string_long(ssl) << "<==" << ret; - if(where & SSL_CB_HANDSHAKE_DONE) { - X509 *ssl_client_cert = SSL_get_peer_certificate(ssl); - if(!ssl_client_cert) - throw std::string("Unable to get peer certificate."); - X509_free(ssl_client_cert); - if(SSL_get_verify_result(ssl) != X509_V_OK) - throw std::string("Certificate verification failed."); - coreutils::Log(coreutils::LOG_DEBUG_3) << "Certificate verified successfully."; - } - else - coreutils::Log(coreutils::LOG_DEBUG_3) << "No client certificate."; - } - - TLS::TLS() {} - - TLS::TLS(TLSInfo *tlsInfo) { - createContext(tlsInfo); - } - - TLS::TLS(SSL_CTX *ctx) : ctx(ctx) {} - - TLS::~TLS() {} - - void TLS::createContext(TLSInfo *tlsInfo) { - - if(tlsInfo) { - SSL_library_init(); - SSL_load_error_strings(); - lockarray = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); - for(int i = 0; i < CRYPTO_num_locks(); ++i) - pthread_mutex_init(&(lockarray[i]), NULL); - CRYPTO_set_id_callback((unsigned long (*)())thread_id); - CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))lock_callback); - SSLeay_add_ssl_algorithms(); - RAND_load_file("/dev/hwrng", 1024); - if(!(ctx = SSL_CTX_new(SSLv23_server_method()))) - throw coreutils::Exception("Error while setting server method SSLv23."); - SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); - // SSL_CTX_set_generate_session_id(ctx, generate_session_id); - SSL_CTX_set_cipher_list(ctx, "ECDH-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA"); - if(SSL_CTX_use_certificate_file(ctx, tlsInfo->certificate.c_str(), SSL_FILETYPE_PEM) <= 0) - throw coreutils::Exception("Error looking up certificate."); - if(SSL_CTX_use_PrivateKey_file(ctx, tlsInfo->key.c_str(), SSL_FILETYPE_PEM) < 0) - throw coreutils::Exception("Error with private key."); - if(SSL_CTX_check_private_key(ctx) != 1) - throw coreutils::Exception("Private key does not match certificate."); - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - SSL_CTX_set_verify_depth(ctx, 1); - if(!SSL_CTX_load_verify_locations(ctx, tlsInfo->cACertificate.c_str(), NULL)) - throw coreutils::Exception("Cannot verify locations."); - SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(tlsInfo->cACertificate.c_str())); - coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated."; - } - } - - void TLS::registerSocket(int fd) { - initialized = true; - int ret; - - coreutils::Log(coreutils::LOG_DEBUG_3) << "TLS socket initializing on socket " << fd << "..."; - - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - - ssl = SSL_new(ctx); -// if(ssl <= 0) -// throw std::string("Error creating new TLS socket."); - - SSL_set_info_callback(ssl, handshake_complete); - - if((ret = SSL_set_fd(ssl, fd)) == 0) - throw std::string("Error setting TLS socket descriptor."); - -// if(!SSL_set_generate_session_id(ssl, generate_session_id)) -// throw std::string("Error setting session identifier callback."); - - } - - void TLS::acceptSocket() { - - switch (SSL_get_error(ssl, SSL_accept(ssl))) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_accept. errno=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_accept."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_accept."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_accept. errno=" << errno; -// shutdown(); - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_accept."; - break; - } - - } - - int TLS::receiveData(coreutils::ZString &buffer) { - - int len; - coreutils::Log(coreutils::LOG_DEBUG_3) << "receiveData TLS on socket."; - - if((len = ::SSL_read(ssl, buffer.getData(), buffer.getLength())) >= 0) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "receiveData TLS...len=" << len; - return len; - } - else { - switch (SSL_get_error(ssl, len)) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_read. error=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_read."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_read."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_read. errno=" << errno; - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_read."; - break; - } - return -1; - } - - } - - int TLS::sendData(const char *data, int length) { - - int len; - - if((len = ::SSL_write(ssl, data, length)) >= 0) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "sendData TLS...len=" << len; - return len; - } - else { - switch (SSL_get_error(ssl, len)) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_write. error=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_write."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_write."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_write. errno=" << errno; - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_write."; - break; - } - return -1; - } - - } - -} - \ No newline at end of file diff --git a/TLS.cpp.pre-wantread-fix-20260905 b/TLS.cpp.pre-wantread-fix-20260905 deleted file mode 100644 index 0e23614..0000000 --- a/TLS.cpp.pre-wantread-fix-20260905 +++ /dev/null @@ -1,280 +0,0 @@ -#include "TLS.h" -#include "Exception.h" -#include "Log.h" -#include -#include - -namespace core { - - static pthread_mutex_t *lockarray; - - static unsigned long thread_id(void) { - return ((unsigned long) pthread_self()); - } - - static void lock_callback(int mode, int type, const char *file, int line) { - if(mode & CRYPTO_LOCK) - pthread_mutex_lock(&(lockarray[type])); - else - pthread_mutex_unlock(&(lockarray[type])); - } - - static int generate_session_id(const SSL *ssl, unsigned char *id, unsigned int *id_len) { - char *session_id_prefix = (char *)"BARANT"; - unsigned int count = 0; - coreutils::Log(coreutils::LOG_DEBUG_3) << "Generating unique session id."; - do { - RAND_bytes(id, *id_len); - memcpy(id, session_id_prefix, (strlen(session_id_prefix) < *id_len)); - } while(SSL_has_matching_session_id(ssl, id, *id_len) && (++count < 10)); - return 1; - } - - void handshake_complete(const SSL *ssl, int where, int ret) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "==>" << SSL_state_string_long(ssl) << "<==" << ret; - if(where & SSL_CB_HANDSHAKE_DONE) { - // Only a context configured with SSL_VERIFY_PEER (see - // createContext()'s own cACertificate-gated block) ever sends a - // client a CertificateRequest in the first place - ordinary - // one-way TLS (a public site's normal case, no caCertificate - // configured) never asks the client for one, so - // SSL_get_peer_certificate() is unconditionally NULL there and is - // not a failure. Checking the mode actually negotiated on THIS - // connection (inherited from the context) rather than assuming - // every handshake is mutual-TLS is what makes plain - // browser-facing HTTPS work at all under this callback. - if(SSL_get_verify_mode(ssl) & SSL_VERIFY_PEER) { - X509 *ssl_client_cert = SSL_get_peer_certificate(ssl); - if(!ssl_client_cert) - throw std::string("Unable to get peer certificate."); - X509_free(ssl_client_cert); - if(SSL_get_verify_result(ssl) != X509_V_OK) - throw std::string("Certificate verification failed."); - coreutils::Log(coreutils::LOG_DEBUG_3) << "Certificate verified successfully."; - } - } - else - coreutils::Log(coreutils::LOG_DEBUG_3) << "No client certificate."; - } - - TLS::TLS() {} - - TLS::TLS(TLSInfo *tlsInfo) { - createContext(tlsInfo); - } - - TLS::TLS(SSL_CTX *ctx) : ctx(ctx) {} - - TLS::~TLS() {} - - void TLS::createContext(TLSInfo *tlsInfo) { - - if(tlsInfo) { - SSL_library_init(); - SSL_load_error_strings(); - lockarray = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); - for(int i = 0; i < CRYPTO_num_locks(); ++i) - pthread_mutex_init(&(lockarray[i]), NULL); - CRYPTO_set_id_callback((unsigned long (*)())thread_id); - CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))lock_callback); - SSLeay_add_ssl_algorithms(); - RAND_load_file("/dev/hwrng", 1024); - if(!(ctx = SSL_CTX_new(SSLv23_server_method()))) - throw coreutils::Exception("Error while setting server method SSLv23."); - SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); - // SSL_CTX_set_generate_session_id(ctx, generate_session_id); - SSL_CTX_set_cipher_list(ctx, "ECDH-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA"); - // _chain_file (not _file) - loads every certificate in the file, - // not just the first. A real Let's Encrypt fullchain.pem is the - // leaf cert followed by the intermediate CA cert concatenated in - // one file specifically so a server can hand both to the client - // in one shot; SSL_CTX_use_certificate_file() only ever reads the - // first PEM block, silently dropping the intermediate - confirmed - // via a real handshake against this exact fullchain.pem: clients - // got "unable to get local issuer certificate" because the server - // only ever sent the leaf, never the intermediate needed to chain - // up to a root the client already trusts. - if(SSL_CTX_use_certificate_chain_file(ctx, tlsInfo->certificate.c_str()) <= 0) - throw coreutils::Exception("Error looking up certificate."); - if(SSL_CTX_use_PrivateKey_file(ctx, tlsInfo->key.c_str(), SSL_FILETYPE_PEM) < 0) - throw coreutils::Exception("Error with private key."); - if(SSL_CTX_check_private_key(ctx) != 1) - throw coreutils::Exception("Private key does not match certificate."); - // Client-certificate verification (mutual TLS) is opt-in, not the - // default - a public-facing site (e.g. a real Let's Encrypt - // server cert with no corresponding client CA of its own) has no - // client certificate to hand out to ordinary visitors, so - // requesting one via SSL_VERIFY_PEER would surface a "select a - // certificate" prompt real browsers can't meaningfully answer. - // Only enable it when a caCertificate is actually configured - - // existing internal/mTLS use of this class (any config that DOES - // set tls.caCertificate) keeps behaving exactly as before. - if(tlsInfo->cACertificate.getLength() > 0) { - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - SSL_CTX_set_verify_depth(ctx, 1); - if(!SSL_CTX_load_verify_locations(ctx, tlsInfo->cACertificate.c_str(), NULL)) - throw coreutils::Exception("Cannot verify locations."); - SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(tlsInfo->cACertificate.c_str())); - coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (client certificate required)."; - } else { - coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (no client certificate required)."; - } - } - } - - void TLS::registerSocket(int fd) { - int ret; - - coreutils::Log(coreutils::LOG_DEBUG_3) << "TLS socket initializing on socket " << fd << "..."; - - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - - ssl = SSL_new(ctx); -// if(ssl <= 0) -// throw std::string("Error creating new TLS socket."); - - SSL_set_info_callback(ssl, handshake_complete); - - if((ret = SSL_set_fd(ssl, fd)) == 0) - throw std::string("Error setting TLS socket descriptor."); - -// if(!SSL_set_generate_session_id(ssl, generate_session_id)) -// throw std::string("Error setting session identifier callback."); - - // Was set unconditionally as the FIRST statement in this - // function (before `ssl` was even assigned) - a flag meant to - // mean "ssl is a valid, ready-to-use SSL*" needs to become true - // only once that's actually true, at the end, not the start. - // Previously set but never actually READ anywhere either - - // sendData()/receiveData() below now check it before touching - // `ssl` at all. See their own doc comments for the real, - // confirmed crash this fixes: a brand-new TLS session's own - // greeting (queued in onConnected(), see e.g. IMAPSession.h) - // gets written via Socket::write() -> resetSocket(), which - deep - // inside TCPServer::accept(), BEFORE TCPServer::connectionRequest() - // ever calls this session's own performTLSHandshake() - makes the - // fd immediately eligible for an EPOLLWRNORM event on a SEPARATE - // epoll worker thread. That thread's TCPSocket::sendData() takes - // ioLock (see its own doc comment) and calls straight into - // SSL_write() on `ssl`, which is genuinely uninitialized garbage - // (TLS.h explicitly documents `ssl` has no default member - // initializer) until THIS function runs - ioLock alone doesn't - // prevent it, since performTLSHandshake() (the only other place - // that takes ioLock) hasn't even been called yet at that point. - // Confirmed live via gdb under a mixed-protocol connection-churn - // stress test: SIGSEGV inside libssl.so's SSL_write(), called from - // TCPSocket::sendData() with ioGuard already held, while - // registerSocket()/acceptSocket() for that same session were still - // pending on the accepting thread. - initialized = true; - } - - void TLS::acceptSocket() { - - switch (SSL_get_error(ssl, SSL_accept(ssl))) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_accept. errno=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_accept."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_accept."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_accept. errno=" << errno; -// shutdown(); - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_accept."; - break; - } - - } - - int TLS::receiveData(coreutils::ZString &buffer) { - - // `ssl` is genuinely uninitialized until registerSocket() has - // fully run (see its own doc comment on `initialized` for the - // real crash this prevents) - a racing thread landing here first - // just gets "nothing yet", exactly like a real SSL_ERROR_WANT_READ - // would report, rather than touching a garbage pointer. - if (!initialized) - return -1; - - int len; - coreutils::Log(coreutils::LOG_DEBUG_3) << "receiveData TLS on socket."; - - if((len = ::SSL_read(ssl, buffer.getData(), buffer.getLength())) >= 0) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "receiveData TLS...len=" << len; - return len; - } - else { - switch (SSL_get_error(ssl, len)) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_read. error=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_read."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_read."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_read. errno=" << errno; - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_read."; - break; - } - return -1; - } - - } - - int TLS::sendData(const char *data, int length) { - - // Same "ssl isn't ready yet" guard as receiveData() - see - // registerSocket()'s doc comment on `initialized` for the real, - // confirmed crash this prevents (SIGSEGV in SSL_write() on a - // still-uninitialized `ssl`, reached via a brand-new session's own - // greeting racing ahead of its own TLS handshake). Returning <= 0 - // here is already the exact case Socket::writeSocket() treats as - // "nothing sent this round, leave it queued and retry on the next - // EPOLLWRNORM" - no special-casing needed at any call site. - if (!initialized) - return -1; - - int len; - - if((len = ::SSL_write(ssl, data, length)) >= 0) { - coreutils::Log(coreutils::LOG_DEBUG_3) << "sendData TLS...len=" << len; - return len; - } - else { - switch (SSL_get_error(ssl, len)) { - case SSL_ERROR_SSL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SSL on ssl_write. error=" << errno; - break; - case SSL_ERROR_WANT_READ: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_READ on ssl_write."; - break; - case SSL_ERROR_WANT_WRITE: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_WANT_WRITE on ssl_write."; - break; - case SSL_ERROR_SYSCALL: - coreutils::Log(coreutils::LOG_DEBUG_3) << "ERROR_SYSCALL on ssl_write. errno=" << errno; - break; - default: - coreutils::Log(coreutils::LOG_DEBUG_3) << "Unknown ERROR on ssl_write."; - break; - } - return -1; - } - - } - -} - \ No newline at end of file diff --git a/TLS.h.pre-wantread-fix-20260905 b/TLS.h.pre-wantread-fix-20260905 deleted file mode 100644 index 3bfdfb4..0000000 --- a/TLS.h.pre-wantread-fix-20260905 +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __TLS_h__ -#define __TLS_h__ - -#include "ZString.h" -#include "TLSInfo.h" -#include - -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); - - // 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(); - - 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; - - }; - -} - -#endif