#include "TCPSession.h" #include "Exception.h" #include "Log.h" #include "TCPServer.h" #include "uuid/uuid.h" namespace core { TCPSession::TCPSession(EPoll &ePoll, std::string text) : TCPSocket(ePoll, text) { // uuid_t is 16 raw binary bytes, not a NUL-terminated string - // streaming it directly (the old code did `<< uuidObject`) hits // libstdc++'s unsigned-char* operator<< overload, which treats its // argument as a C-string and calls strlen() on it: a real // stack-buffer-overflow read past the 16-byte local, confirmed via // AddressSanitizer under concurrent load. Separately, `alias` was // being pointed at `uuidObject` itself - a stack-local array that is // destroyed the instant this constructor returns, leaving `alias` a // dangling pointer for the rest of the session's life (used by // compareAlias() for session identity). uuid_unparse() into the // `uuid` member (declared for exactly this - see its doc comment) // fixes both: a real NUL-terminated string safe to log, backed by // storage that lives as long as the TCPSession does. uuid_t uuidObject; uuid_generate(uuidObject); uuid_unparse(uuidObject, uuid); coreutils::Log(coreutils::LOG_DEBUG_1) << uuid; alias = (void *)uuid; } TCPSession::TCPSession(EPoll &ePoll, TCPServer *server, std::string text) : TCPSocket(ePoll, server->ctx, text), server(server) { uuid_t uuidObject; uuid_generate(uuidObject); uuid_unparse(uuidObject, uuid); coreutils::Log(coreutils::LOG_DEBUG_1) << uuid; alias = (void *)uuid; } TCPSession::~TCPSession() { server->removeFromSessionList(this); server->subscriptions.removeSessionSubscriptions(*this); } void TCPSession::output(std::stringstream &data) { data << "|" << ipAddress.getClientAddressAndPort(); } void TCPSession::protocol(coreutils::ZString &data) { // The data.getLength() != 0 guard alone silently swallowed every // blank line a client sent, before it ever reached // CommandList::processRequest() - fine for ordinary command // dispatch (a stray blank line between commands isn't a command), // but CommandList::processRequest() already special-cases // session.grab != NULL to hand the line to the grabbed handler // unconditionally, blank or not - a handler mid-grabInput() (e.g. // __SMTP_DATA reading a message body, or an IMAP APPEND literal) // depends on seeing blank lines as real content, not commands. // With the outer guard unconditional, every message body's // header/body separator - and every blank line within the body // itself - was getting dropped before that handler ever saw it. // Confirmed via a delivered test message with zero CRLFCRLF // sequences anywhere in it, which is what first exposed this. if (grab != NULL || data.getLength() != 0) { if (!server->commands.processRequest(data, *this)) { coreutils::Log(coreutils::LOG_DEBUG_1) << "Received data could not be parsed: " << data.str(); } } } bool TCPSession::compareAlias(void *alias) { return this->alias = alias; } void TCPSession::outputAlias(std::stringstream &out) { out << alias; } void TCPSession::onRegistered() { onConnected(); send(); if (term) shutdown("termination requested"); } void TCPSession::onConnected() {} void TCPSession::onLineReceived(coreutils::ZString &line) { protocol(line); send(); if (term) shutdown("termination requested"); } void TCPSession::send() { if(out.tellp() > 0) write(out.str()); out.str(""); } void TCPSession::terminate() { term = true; } }