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.
This commit is contained in:
1 parent
3609ed603e
commit
381ed86875
23 files changed
+1
-3527
No files matched your search
+1
-1
@@ -11,4 +11,4 @@ docs/html
|
|||||||
*/mmap_address.bin
|
*/mmap_address.bin
|
||||||
.history/*
|
.history/*
|
||||||
html
|
html
|
||||||
latex
|
latex*.pre-*
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#include "CommandList.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
#include "Socket.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "ZString.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
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<std::mutex> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
#include "Socket.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "ZString.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
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<std::mutex> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
#include "Socket.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "ZString.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
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<std::mutex> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
#ifndef __Socket_h__
|
|
||||||
#define __Socket_h__
|
|
||||||
|
|
||||||
#include "ZString.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <mutex>
|
|
||||||
#include <sys/epoll.h>
|
|
||||||
|
|
||||||
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); ///<Set the descriptor for the socket.
|
|
||||||
|
|
||||||
int getDescriptor(); ///< Get the descriptor for the socket.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The event received from epoll is sent through the eventReceived
|
|
||||||
/// method which will parse the event and call the read and write
|
|
||||||
/// callbacks on the socket.
|
|
||||||
///
|
|
||||||
/// This method is called by the BMAEPoll object and should not be called
|
|
||||||
/// from any user extended classes unless an epoll event is being
|
|
||||||
/// simulated.
|
|
||||||
///
|
|
||||||
/// The return value of false will delete the socket object causing the destructors to run.
|
|
||||||
/// The return value of true will enable the socket on ePoll to receive more events.
|
|
||||||
///
|
|
||||||
|
|
||||||
bool eventReceived(struct epoll_event event, long long eventId); ///< Parse epoll event and call specified callbacks.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Write data to the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
int write(std::string data);
|
|
||||||
void write(char *buffer, int length);
|
|
||||||
|
|
||||||
void output(std::stringstream &out);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onRegister method is called before the socket is registered with
|
|
||||||
/// ePoll so objects extending the Socket definition can initialize the socket
|
|
||||||
/// before receiving events. Evoked when the
|
|
||||||
/// descriptor is set using setDescriptor for the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onRegister(); ///< Called before the socket has registered with the epoll processing.
|
|
||||||
virtual void onRegistered(); ///< Called after the socket has been registered with epoll processing.
|
|
||||||
|
|
||||||
virtual void onUnregister();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ePoll and socket communcation events will be stopped. The default method will
|
|
||||||
/// close the socket and clean up the connection. If this is overridden by an
|
|
||||||
/// extended object then the object should call this method to clean the socket up.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onUnregistered(); ///< Called when the socket has finished unregistering for the epoll processing.
|
|
||||||
|
|
||||||
bool needsToWrite();
|
|
||||||
|
|
||||||
bool reset = false;
|
|
||||||
|
|
||||||
volatile bool shutDown = false;
|
|
||||||
|
|
||||||
void enableSocket();
|
|
||||||
void disableSocket();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
EPoll &ePoll; // The EPoll control object.
|
|
||||||
|
|
||||||
void setBufferSize(int length);
|
|
||||||
|
|
||||||
int getBufferSize();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onConnected method is called when the socket is ready to communicate.
|
|
||||||
/// Writing to the socket can begin on this call to initiate a contact with the
|
|
||||||
/// remote device.
|
|
||||||
///
|
|
||||||
|
|
||||||
// virtual void onConnected(); ///< Called when socket is open and ready to communicate.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The connectionRequest method is called to instantiate a new session handler object
|
|
||||||
/// and form a new connection to a listening server.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void connectionRequest();
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
// virtual void onDisconnected(); ///< Called when socket is closing and no longer ready to communicate.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onDataReceived method is called when the socket has received an event from
|
|
||||||
/// epoll and there is data ready to be read from the socket. The default handler
|
|
||||||
/// will pull the data and put it into the streambuf for the socket. EPOLLIN
|
|
||||||
///
|
|
||||||
/// @param data the data that has been received from the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onDataReceived(std::string data); ///< Called when data is received from the socket.
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onDataReceived(coreutils::ZString &data);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// receiveData will read the data from the socket and place it in the socket buffer.
|
|
||||||
/// TLS layer overrides this to be able to read from SSL.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void receiveData(coreutils::ZString &buffer);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// sendData writes data to the socket descriptor. TLS layer overrides this to
|
|
||||||
/// write through SSL instead of a raw socket write.
|
|
||||||
///
|
|
||||||
/// @return The number of bytes written, or -1 on error.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual int sendData(const char *data, int length);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
int descriptor = -1;
|
|
||||||
std::mutex outlock;
|
|
||||||
bool readHangup = false;
|
|
||||||
volatile bool inHandler = false;
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------
|
|
||||||
// the writeSocket is called when epoll has received a write request for a socket.
|
|
||||||
// Writing data to this socket is queued in the streambuf and permission is requested
|
|
||||||
// to write to the socket. This routine handles the writing of the streambuf data
|
|
||||||
// buffer to the socket.
|
|
||||||
//-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void writeSocket();
|
|
||||||
|
|
||||||
// int_type underflow();
|
|
||||||
// int_type uflow();
|
|
||||||
// int_type pbackfail(int_type ch);
|
|
||||||
// streamsize showmanyc();
|
|
||||||
|
|
||||||
char *buffer; // This is a pointer to the managed buffer space.
|
|
||||||
int length; // This is the length of the buffer.
|
|
||||||
|
|
||||||
std::queue<std::string> 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
|
|
||||||
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
#ifndef __Socket_h__
|
|
||||||
#define __Socket_h__
|
|
||||||
|
|
||||||
#include "ZString.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <mutex>
|
|
||||||
#include <sys/epoll.h>
|
|
||||||
|
|
||||||
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); ///<Set the descriptor for the socket.
|
|
||||||
|
|
||||||
int getDescriptor(); ///< Get the descriptor for the socket.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The event received from epoll is sent through the eventReceived
|
|
||||||
/// method which will parse the event and call the read and write
|
|
||||||
/// callbacks on the socket.
|
|
||||||
///
|
|
||||||
/// This method is called by the BMAEPoll object and should not be called
|
|
||||||
/// from any user extended classes unless an epoll event is being
|
|
||||||
/// simulated.
|
|
||||||
///
|
|
||||||
/// The return value of false will delete the socket object causing the destructors to run.
|
|
||||||
/// The return value of true will enable the socket on ePoll to receive more events.
|
|
||||||
///
|
|
||||||
|
|
||||||
bool eventReceived(struct epoll_event event, long long eventId); ///< Parse epoll event and call specified callbacks.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Write data to the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
int write(std::string data);
|
|
||||||
void write(char *buffer, int length);
|
|
||||||
|
|
||||||
void output(std::stringstream &out);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onRegister method is called before the socket is registered with
|
|
||||||
/// ePoll so objects extending the Socket definition can initialize the socket
|
|
||||||
/// before receiving events. Evoked when the
|
|
||||||
/// descriptor is set using setDescriptor for the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onRegister(); ///< Called before the socket has registered with the epoll processing.
|
|
||||||
virtual void onRegistered(); ///< Called after the socket has been registered with epoll processing.
|
|
||||||
|
|
||||||
virtual void onUnregister();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ePoll and socket communcation events will be stopped. The default method will
|
|
||||||
/// close the socket and clean up the connection. If this is overridden by an
|
|
||||||
/// extended object then the object should call this method to clean the socket up.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onUnregistered(); ///< Called when the socket has finished unregistering for the epoll processing.
|
|
||||||
|
|
||||||
bool needsToWrite();
|
|
||||||
|
|
||||||
bool reset = false;
|
|
||||||
|
|
||||||
volatile bool shutDown = false;
|
|
||||||
|
|
||||||
void enableSocket();
|
|
||||||
void disableSocket();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
EPoll &ePoll; // The EPoll control object.
|
|
||||||
|
|
||||||
void setBufferSize(int length);
|
|
||||||
|
|
||||||
int getBufferSize();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onConnected method is called when the socket is ready to communicate.
|
|
||||||
/// Writing to the socket can begin on this call to initiate a contact with the
|
|
||||||
/// remote device.
|
|
||||||
///
|
|
||||||
|
|
||||||
// virtual void onConnected(); ///< Called when socket is open and ready to communicate.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The connectionRequest method is called to instantiate a new session handler object
|
|
||||||
/// and form a new connection to a listening server.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void connectionRequest();
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
// virtual void onDisconnected(); ///< Called when socket is closing and no longer ready to communicate.
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The onDataReceived method is called when the socket has received an event from
|
|
||||||
/// epoll and there is data ready to be read from the socket. The default handler
|
|
||||||
/// will pull the data and put it into the streambuf for the socket. EPOLLIN
|
|
||||||
///
|
|
||||||
/// @param data the data that has been received from the socket.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onDataReceived(std::string data); ///< Called when data is received from the socket.
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onDataReceived(coreutils::ZString &data);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// receiveData will read the data from the socket and place it in the socket buffer.
|
|
||||||
/// TLS layer overrides this to be able to read from SSL.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void receiveData(coreutils::ZString &buffer);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// sendData writes data to the socket descriptor. TLS layer overrides this to
|
|
||||||
/// write through SSL instead of a raw socket write.
|
|
||||||
///
|
|
||||||
/// @return The number of bytes written, or -1 on error.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual int sendData(const char *data, int length);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
int descriptor = -1;
|
|
||||||
std::mutex outlock;
|
|
||||||
bool readHangup = false;
|
|
||||||
volatile bool inHandler = false;
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------
|
|
||||||
// the writeSocket is called when epoll has received a write request for a socket.
|
|
||||||
// Writing data to this socket is queued in the streambuf and permission is requested
|
|
||||||
// to write to the socket. This routine handles the writing of the streambuf data
|
|
||||||
// buffer to the socket.
|
|
||||||
//-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void writeSocket();
|
|
||||||
|
|
||||||
// int_type underflow();
|
|
||||||
// int_type uflow();
|
|
||||||
// int_type pbackfail(int_type ch);
|
|
||||||
// streamsize showmanyc();
|
|
||||||
|
|
||||||
char *buffer; // This is a pointer to the managed buffer space.
|
|
||||||
int length; // This is the length of the buffer.
|
|
||||||
|
|
||||||
std::queue<std::string> fifo;
|
|
||||||
|
|
||||||
void resetSocket();
|
|
||||||
|
|
||||||
std::mutex lock;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -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<TCPSession *>::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<TCPSession *> 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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#include "TCPSocket.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "errno.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
#include "TLS.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
#include "TLS.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#ifndef __TLS_h__
|
|
||||||
#define __TLS_h__
|
|
||||||
|
|
||||||
#include "ZString.h"
|
|
||||||
#include "TLSInfo.h"
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
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
|
|
||||||
Reference in new issue
Block a user