Merge tls into master: bring in subscription ownership, keep all TLS/stability work
tls and master diverged from a July 2024 fork point and never reconciled - tls carries ~19 months of real socket/TLS stability fixes (uninitialized tlsInfo, TCPServer::connectionRequest() unconditionally initializing TLS, the WANT_READ write busy-spin, the console authentication fix from this session), while master independently picked up a Subscription/SubscriptionManager rework (MString-based ownership tracking: owner/ownership/selection/time fields and a working subscriptionOwner() method) that tls never had. Every file both branches touched was resolved by keeping tls's side entirely (Command.h, Socket.cpp, TCPServer.h, TCPSession.cpp/h, TCPSocket.h were pure reformatting on master's side, or an alternate/superseded fix for a bug tls already fixed a different way - e.g. master's TCPSession uuid/alias handling vs tls's existing uuid_unparse-based fix), with two deliberate exceptions: - TCPServer.cpp's constructor was missing the commands.add(subscriptions, "publish"/"subscribe"/"catalog"/...) wiring master had - without it, SubscriptionManager existed but was completely unreachable through the normal command dispatch. Restored on top of tls's TLS-aware constructor. - SubscriptionManager.cpp's "invite" command handler had a real incompatibility: master's version assigned into an `invitee` member typed as coreutils::MString via a stray `(coreutils::MString *)` cast that doesn't match the member's actual (non-pointer) type, and called session.server.getSessionByAlias() against a TCPServer reference - tls made TCPSession::server a pointer (part of removing the separate TCPSession2 class), so that call site no longer compiles as written. Took tls's cleaner local-variable version instead (functionally identical - looks up a session by alias and forwards an invite message) and removed the now-unused `invitee` member from SubscriptionManager.h. One additional compile fix needed after resolving conflicts: Subscription::subscriptionOwners is an MString (master's ownership design), but TCPSession::alias is void* (tls's identity mechanism, unchanged and depended on elsewhere) - MString has an implicit constructor from void* already used successfully elsewhere in this same file (the "publish" handler's Subscription constructor call), but operator== doesn't accept the same implicit conversion, so removeSessionSubscriptions()'s ownership check needed an explicit coreutils::MString(session.alias) construction to compile. Also drops output/main (a stray checked-in compiled binary, already removed on tls) and the doxygen docs/html output (already removed on tls, was carried on master since the fork). Verified: full ServerCore rebuild (./compile) succeeds with zero errors or warnings beyond the one compile fix documented above.
No files matched your search
@@ -1,13 +1,10 @@
|
|||||||
#ifndef __Command_h__
|
#ifndef __Command_h__
|
||||||
#define __Command_h__
|
#define __Command_h__
|
||||||
|
|
||||||
#include "Object.h"
|
|
||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
#include "ZString.h"
|
#include "ZString.h"
|
||||||
#include "includes"
|
|
||||||
|
|
||||||
namespace core
|
namespace core {
|
||||||
{
|
|
||||||
|
|
||||||
class CommandList;
|
class CommandList;
|
||||||
|
|
||||||
@@ -20,10 +17,10 @@ namespace core
|
|||||||
/// a list of functions that can be invoked as a result of processing a request.
|
/// a list of functions that can be invoked as a result of processing a request.
|
||||||
///
|
///
|
||||||
|
|
||||||
class Command
|
class Command {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
/// This method is used to implement the functionality of the requested command.
|
/// This method is used to implement the functionality of the requested command.
|
||||||
/// This pure virtual function must be implemented in your inheriting object.
|
/// This pure virtual function must be implemented in your inheriting object.
|
||||||
@@ -44,6 +41,7 @@ namespace core
|
|||||||
///
|
///
|
||||||
|
|
||||||
virtual void output(std::stringstream &out);
|
virtual void output(std::stringstream &out);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "CommandList.h"
|
#include "CommandList.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -21,7 +23,20 @@ namespace core {
|
|||||||
request.split(delimiter, depth);
|
request.split(delimiter, depth);
|
||||||
request.reset();
|
request.reset();
|
||||||
try {
|
try {
|
||||||
auto command = commands.at(request[0].str());
|
// 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);
|
return command->processCommand(request, session);
|
||||||
}
|
}
|
||||||
catch(...) {
|
catch(...) {
|
||||||
@@ -41,8 +56,15 @@ namespace core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int CommandList::processCommand(coreutils::ZString &request, TCPSession &session) {
|
int CommandList::processCommand(coreutils::ZString &request, TCPSession &session) {
|
||||||
// for(Command *command : commands)
|
// The intent was always "typing help lists the available
|
||||||
// session.out << command->getName() << std::endl;
|
// commands" - this was left as a no-op stub (the commented-out
|
||||||
|
// body even called a Command::getName() that was never added to
|
||||||
|
// the base class) so every registered CommandList - including
|
||||||
|
// the top-level one behind the "help" command itself - silently
|
||||||
|
// did nothing. The map's own key IS the registered name (see
|
||||||
|
// add()), so no getName() is needed at all - just walk it directly.
|
||||||
|
for (auto &pair : commands)
|
||||||
|
session.out << pair.first << std::string("\r\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
#include <map>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
ConsoleServer::ConsoleServer(EPoll &ePoll, IPAddress address) : TCPServer(ePoll, address, " ", 10, "Console Server") {
|
ConsoleServer::ConsoleServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo,
|
||||||
|
std::function<bool(std::string, std::string)> credentialChecker)
|
||||||
|
: TCPServer(ePoll, address, tlsInfo, " ", 10, "Console Server"), credentialChecker(credentialChecker) {
|
||||||
coreutils::Log(this);
|
coreutils::Log(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +17,11 @@ namespace core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TCPSession * ConsoleServer::getSocketAccept(EPoll &ePoll) {
|
TCPSession * ConsoleServer::getSocketAccept(EPoll &ePoll) {
|
||||||
return new ConsoleSession(ePoll, *this);
|
return new ConsoleSession(ePoll, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConsoleServer::checkCredentials(std::string user, std::string password) {
|
||||||
|
return credentialChecker ? credentialChecker(user, password) : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#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,11 +1,13 @@
|
|||||||
#ifndef __ConsoleServer_h__
|
#ifndef __ConsoleServer_h__
|
||||||
# define __ConsoleServer_h__
|
# define __ConsoleServer_h__
|
||||||
|
|
||||||
#include "includes"
|
# include "TCPServer.h"
|
||||||
#include "TLSServer.h"
|
|
||||||
# include "Command.h"
|
# include "Command.h"
|
||||||
# include "EPoll.h"
|
# include "EPoll.h"
|
||||||
# include "LogListener.h"
|
# include "LogListener.h"
|
||||||
|
# include "TLSInfo.h"
|
||||||
|
# include <functional>
|
||||||
|
# include <string>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -21,10 +23,16 @@ namespace core {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
// credentialChecker, when provided, is consulted by
|
||||||
|
// ConsoleSession's login flow (see its own doc comment) instead
|
||||||
|
// of the previous unconditional accept-anything behavior. Left
|
||||||
|
// defaulting to nullptr so any other, unrelated use of
|
||||||
|
// ConsoleServer that never cared about authentication is
|
||||||
|
// unaffected.
|
||||||
//
|
//
|
||||||
|
|
||||||
ConsoleServer(EPoll &ePoll, IPAddress address);
|
ConsoleServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo,
|
||||||
|
std::function<bool(std::string, std::string)> credentialChecker = nullptr);
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -34,6 +42,11 @@ namespace core {
|
|||||||
|
|
||||||
TCPSession * getSocketAccept(EPoll &ePoll) override;
|
TCPSession * getSocketAccept(EPoll &ePoll) override;
|
||||||
|
|
||||||
|
bool checkCredentials(std::string user, std::string password);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<bool(std::string, std::string)> credentialChecker;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#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,10 +1,11 @@
|
|||||||
#include "ConsoleSession.h"
|
#include "ConsoleSession.h"
|
||||||
|
#include "ConsoleServer.h"
|
||||||
#include "TCPServer.h"
|
#include "TCPServer.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
ConsoleSession::ConsoleSession(EPoll &ePoll, TCPServer &server) : TerminalSession(ePoll, server) {}
|
ConsoleSession::ConsoleSession(EPoll &ePoll, TCPServer *server) : TerminalSession(ePoll, server) {}
|
||||||
|
|
||||||
ConsoleSession::~ConsoleSession() {}
|
ConsoleSession::~ConsoleSession() {}
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ namespace core {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_USER_PROFILE:
|
case WAIT_USER_PROFILE:
|
||||||
|
// Previously discarded entirely - nothing captured what was
|
||||||
|
// actually typed here, so a real credential check further
|
||||||
|
// down could never have anything to compare against.
|
||||||
|
enteredUser = data.str();
|
||||||
status = PASSWORD;
|
status = PASSWORD;
|
||||||
protocol(blank);
|
protocol(blank);
|
||||||
break;
|
break;
|
||||||
@@ -37,8 +42,21 @@ namespace core {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_PASSWORD:
|
case WAIT_PASSWORD:
|
||||||
|
// The actual check: previously this state unconditionally
|
||||||
|
// advanced to PROMPT regardless of what (if anything) was
|
||||||
|
// entered at either step - any username/password, including
|
||||||
|
// blank, reached the command prompt. checkCredentials()
|
||||||
|
// returns true unconditionally when the owning ConsoleServer
|
||||||
|
// was built without a credentialChecker, preserving that
|
||||||
|
// exact behavior for any other use of this class.
|
||||||
|
if (((ConsoleServer *)server)->checkCredentials(enteredUser, data.str())) {
|
||||||
status = PROMPT;
|
status = PROMPT;
|
||||||
protocol(blank);
|
protocol(blank);
|
||||||
|
} else {
|
||||||
|
out << "Login failed." << "\r\n";
|
||||||
|
status = DONE;
|
||||||
|
protocol(blank);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PROMPT:
|
case PROMPT:
|
||||||
@@ -68,6 +86,11 @@ namespace core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConsoleSession::onConnected() {
|
||||||
|
coreutils::ZString blank("");
|
||||||
|
protocol(blank);
|
||||||
|
}
|
||||||
|
|
||||||
void ConsoleSession::writeLog(std::string data) {
|
void ConsoleSession::writeLog(std::string data) {
|
||||||
// saveCursor();
|
// saveCursor();
|
||||||
// setCursorLocation(16, 1);
|
// setCursorLocation(16, 1);
|
||||||
@@ -75,7 +98,7 @@ namespace core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleSession::doCommand(coreutils::ZString &request) {
|
void ConsoleSession::doCommand(coreutils::ZString &request) {
|
||||||
server.commands.processRequest(request, *this);
|
server->commands.processRequest(request, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,11 +16,19 @@ namespace core {
|
|||||||
/// Extend the protocol() method in order to define the behavior and
|
/// Extend the protocol() method in order to define the behavior and
|
||||||
/// protocol interaction for this socket which is a console session.
|
/// protocol interaction for this socket which is a console session.
|
||||||
///
|
///
|
||||||
|
/// The User:/Password: exchange is a real login when the owning
|
||||||
|
/// ConsoleServer was constructed with a credentialChecker (see its
|
||||||
|
/// own doc comment) - previously the entered username was never
|
||||||
|
/// even stored and the password was never compared against
|
||||||
|
/// anything, so any input (including blank) reached the command
|
||||||
|
/// prompt. A ConsoleServer built without a credentialChecker still
|
||||||
|
/// accepts anything, matching that original behavior exactly.
|
||||||
|
///
|
||||||
|
|
||||||
class ConsoleSession : public TerminalSession {
|
class ConsoleSession : public TerminalSession {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ConsoleSession(EPoll &ePoll, TCPServer &server);
|
ConsoleSession(EPoll &ePoll, TCPServer *server);
|
||||||
~ConsoleSession();
|
~ConsoleSession();
|
||||||
|
|
||||||
void writeLog(std::string data);
|
void writeLog(std::string data);
|
||||||
@@ -28,11 +36,27 @@ namespace core {
|
|||||||
protected:
|
protected:
|
||||||
void protocol(coreutils::ZString &data) override;
|
void protocol(coreutils::ZString &data) override;
|
||||||
|
|
||||||
|
// Previously absent entirely, so the state machine only ever
|
||||||
|
// advanced on the CLIENT's first line of input - that line was
|
||||||
|
// consumed just to drive WELCOME->LOGIN and was never used as
|
||||||
|
// real content, meaning every subsequent line was shifted one
|
||||||
|
// slot early (what the user typed as their *username* actually
|
||||||
|
// got compared as nothing, their *password* got captured as
|
||||||
|
// enteredUser, and their first real command got compared as the
|
||||||
|
// password). Harmless before real credential checking existed -
|
||||||
|
// any input reached the prompt regardless - but it broke login
|
||||||
|
// outright once checkCredentials() started actually comparing
|
||||||
|
// values. Kicking the state machine off here instead means
|
||||||
|
// "User: " is sent immediately on connect, before the client
|
||||||
|
// has sent anything at all.
|
||||||
|
void onConnected() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Status {WELCOME, LOGIN, WAIT_USER_PROFILE, PASSWORD, WAIT_PASSWORD, PROMPT, INPUT, PROCESS, DONE};
|
enum Status {WELCOME, LOGIN, WAIT_USER_PROFILE, PASSWORD, WAIT_PASSWORD, PROMPT, INPUT, PROCESS, DONE};
|
||||||
Status status = WELCOME;
|
Status status = WELCOME;
|
||||||
void doCommand(coreutils::ZString &request);
|
void doCommand(coreutils::ZString &request);
|
||||||
coreutils::ZString command;
|
coreutils::ZString command;
|
||||||
|
std::string enteredUser;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#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
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "Exception.h"
|
#include "Exception.h"
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -13,11 +14,27 @@ namespace core {
|
|||||||
maxSockets = 1000;
|
maxSockets = 1000;
|
||||||
epfd = epoll_create1(0);
|
epfd = epoll_create1(0);
|
||||||
terminateThreads = false;
|
terminateThreads = false;
|
||||||
|
started = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
EPoll::~EPoll() {}
|
// Was empty - if this EPoll's worker pool was started but stop() was
|
||||||
|
// never explicitly called (e.g. an exception unwinding the stack
|
||||||
|
// during startup, before the caller's own normal shutdown path could
|
||||||
|
// run), the pool just kept running against this object's - and every
|
||||||
|
// Socket registered with it's - soon-to-be-freed memory. stop() itself
|
||||||
|
// is what actually joins every worker thread before anything gets
|
||||||
|
// torn down; see Thread::~Thread()'s doc comment for the crash this
|
||||||
|
// was confirmed to cause. Guarded by `started` so a never-started
|
||||||
|
// EPoll (numberOfThreads/threads still default) doesn't call stop()
|
||||||
|
// over a garbage thread count, and by `!terminateThreads` so an
|
||||||
|
// EPoll that already went through a normal stop() doesn't do it
|
||||||
|
// (and close an already-closed epfd) twice.
|
||||||
|
EPoll::~EPoll() {
|
||||||
|
if (started && !terminateThreads)
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
bool EPoll::start(int numberOfThreads, int maxSockets) {
|
bool EPoll::start(int numberOfThreads, int maxSockets, ThreadScope *scope) {
|
||||||
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_2) << "Starting epoll event processing.";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "Starting epoll event processing.";
|
||||||
|
|
||||||
@@ -31,15 +48,20 @@ namespace core {
|
|||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
// Create thread objects into vector for number of threads requested.
|
// Create thread objects into vector for number of threads requested.
|
||||||
// Hand all the threads a pointer to the EPoll object so they can run
|
// Hand all the threads a pointer to the EPoll object so they can run
|
||||||
// the socket handlers.
|
// the socket handlers - and, if given, the same shared ThreadScope
|
||||||
|
// so each one runs its onThreadStart()/onThreadEnd() hooks (see
|
||||||
|
// ThreadScope's own doc comment for why - e.g. MySQL client library
|
||||||
|
// per-thread init).
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
for(int ix = 0; ix < numberOfThreads; ++ix)
|
for(int ix = 0; ix < numberOfThreads; ++ix)
|
||||||
threads.emplace_back(*this);
|
threads.emplace_back(*this, scope);
|
||||||
|
|
||||||
for(int ix = 0; ix < numberOfThreads; ++ix)
|
for(int ix = 0; ix < numberOfThreads; ++ix)
|
||||||
threads[ix].start();
|
threads[ix].start();
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +95,16 @@ namespace core {
|
|||||||
|
|
||||||
int EPoll::processCommand(coreutils::ZString &request, TCPSession &session) {
|
int EPoll::processCommand(coreutils::ZString &request, TCPSession &session) {
|
||||||
int sequence = 0;
|
int sequence = 0;
|
||||||
for(auto threadx : threads) {
|
// Was `for(auto threadx : threads)` - copied each live, running
|
||||||
|
// Thread by value. Harmless while ~Thread() was empty, but now
|
||||||
|
// that it actually joins and deletes the underlying std::thread*
|
||||||
|
// (see its own doc comment), a copy going out of scope at the end
|
||||||
|
// of each iteration would join and free the SAME thread the real
|
||||||
|
// vector entry still owns and expects to keep running - a
|
||||||
|
// double-free the next time anything lists threads on a live
|
||||||
|
// server (this method is reachable via the console's own command
|
||||||
|
// dispatch).
|
||||||
|
for(auto &threadx : threads) {
|
||||||
session.out << "|" << ++sequence;
|
session.out << "|" << ++sequence;
|
||||||
threadx.output(session.out);
|
threadx.output(session.out);
|
||||||
session.out << "|" << std::endl;
|
session.out << "|" << std::endl;
|
||||||
|
|||||||
@@ -51,9 +51,13 @@ namespace core {
|
|||||||
///
|
///
|
||||||
/// @param numberOfThreads the number of threads to start for processing epoll entries.
|
/// @param numberOfThreads the number of threads to start for processing epoll entries.
|
||||||
/// @param maxSockets the maximum number of open sockets that epoll will manage.
|
/// @param maxSockets the maximum number of open sockets that epoll will manage.
|
||||||
|
/// @param scope optional - if given, its onThreadStart()/onThreadEnd() run once on
|
||||||
|
/// each worker thread, at the top/bottom of that thread's own lifetime. The SAME
|
||||||
|
/// scope instance is shared across every worker thread - see ThreadScope's own
|
||||||
|
/// doc comment. Not owned by EPoll; caller keeps it alive for the server's lifetime.
|
||||||
///
|
///
|
||||||
|
|
||||||
bool start(int numberOfThreads, int maxSockets); ///< Start the BMAEPoll processing.
|
bool start(int numberOfThreads, int maxSockets, ThreadScope *scope = NULL); ///< Start the BMAEPoll processing.
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Use the stop() method to initiate the shutdown process for the epoll socket management.
|
/// Use the stop() method to initiate the shutdown process for the epoll socket management.
|
||||||
@@ -103,6 +107,12 @@ namespace core {
|
|||||||
int numberOfThreads;
|
int numberOfThreads;
|
||||||
std::vector<Thread> threads;
|
std::vector<Thread> threads;
|
||||||
volatile bool terminateThreads;
|
volatile bool terminateThreads;
|
||||||
|
// Guards ~EPoll()'s own call to stop() - false until start()
|
||||||
|
// actually runs, so a never-started EPoll (numberOfThreads and
|
||||||
|
// threads both still default/empty) doesn't have its destructor
|
||||||
|
// iterate a garbage thread count. See ~EPoll()'s own doc comment
|
||||||
|
// in EPoll.cpp for what this is actually protecting against.
|
||||||
|
bool started;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "INotify.h"
|
#include "INotify.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "ZString.h"
|
#include "ZString.h"
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -27,32 +28,53 @@ namespace core {
|
|||||||
ptr < buffer.getData() + buffer.getLength();
|
ptr < buffer.getData() + buffer.getLength();
|
||||||
ptr += sizeof(struct inotify_event) + event->len) {
|
ptr += sizeof(struct inotify_event) + event->len) {
|
||||||
event = (const struct inotify_event *) ptr;
|
event = (const struct inotify_event *) ptr;
|
||||||
coreutils::ZString name(event->name);
|
|
||||||
|
// event->name only exists - and is only NUL-terminated - when
|
||||||
|
// event->len > 0 (inotify(7): "This field is present only when
|
||||||
|
// the event relates to a file inside a watched directory ...
|
||||||
|
// otherwise ... this field is absent."). For events with
|
||||||
|
// len == 0 (e.g. IN_UNMOUNT, IN_Q_OVERFLOW, IN_IGNORED, or
|
||||||
|
// self-watch events), event->name is not a valid C-string at
|
||||||
|
// all - calling strlen() on it (as the old ZString(const char*)
|
||||||
|
// one-arg constructor did unconditionally here) reads past
|
||||||
|
// whatever garbage follows in the socket's 4096-byte receive
|
||||||
|
// buffer looking for a NUL, a real heap-buffer-overflow read
|
||||||
|
// confirmed via AddressSanitizer under concurrent load (the
|
||||||
|
// eventual heap corruption this caused was previously
|
||||||
|
// misdiagnosed as a MySQL client thread-safety issue, since it
|
||||||
|
// surfaces on whichever EPoll worker thread happens to service
|
||||||
|
// this watcher's socket and corrupts unrelated heap state).
|
||||||
|
// strnlen() bounded by event->len (rather than the one-arg
|
||||||
|
// ZString ctor's unbounded strlen) matches the kernel's actual
|
||||||
|
// guarantee: name is NUL-padded to exactly event->len bytes.
|
||||||
|
coreutils::ZString name = event->len > 0
|
||||||
|
? coreutils::ZString(event->name, strnlen(event->name, event->len))
|
||||||
|
: coreutils::ZString("");
|
||||||
|
|
||||||
if(event->mask & IN_ACCESS)
|
if(event->mask & IN_ACCESS)
|
||||||
inAccess(name);
|
inAccess(name);
|
||||||
if(event->mask & IN_ATTRIB)
|
if(event->mask & IN_ATTRIB)
|
||||||
inAttrib(std::string(event->name));
|
inAttrib(name.str());
|
||||||
if(event->mask & IN_CLOSE_WRITE)
|
if(event->mask & IN_CLOSE_WRITE)
|
||||||
inCloseWrite(std::string(event->name));
|
inCloseWrite(name.str());
|
||||||
if(event->mask & IN_CLOSE_NOWRITE)
|
if(event->mask & IN_CLOSE_NOWRITE)
|
||||||
inCloseNoWrite(std::string(event->name));
|
inCloseNoWrite(name.str());
|
||||||
if(event->mask & IN_CREATE)
|
if(event->mask & IN_CREATE)
|
||||||
inCreate(name);
|
inCreate(name);
|
||||||
if(event->mask & IN_DELETE)
|
if(event->mask & IN_DELETE)
|
||||||
inDelete(std::string(event->name));
|
inDelete(name.str());
|
||||||
if(event->mask & IN_DELETE_SELF)
|
if(event->mask & IN_DELETE_SELF)
|
||||||
inDeleteSelf(std::string(event->name));
|
inDeleteSelf(name.str());
|
||||||
if(event->mask & IN_MODIFY)
|
if(event->mask & IN_MODIFY)
|
||||||
inModify(std::string(event->name));
|
inModify(name.str());
|
||||||
if(event->mask & IN_MOVE_SELF)
|
if(event->mask & IN_MOVE_SELF)
|
||||||
inMoveSelf(std::string(event->name));
|
inMoveSelf(name.str());
|
||||||
if(event->mask & IN_MOVED_FROM)
|
if(event->mask & IN_MOVED_FROM)
|
||||||
inMovedFrom(std::string(event->name));
|
inMovedFrom(name.str());
|
||||||
if(event->mask & IN_MOVED_TO)
|
if(event->mask & IN_MOVED_TO)
|
||||||
inMovedTo(std::string(event->name));
|
inMovedTo(name.str());
|
||||||
if(event->mask & IN_OPEN)
|
if(event->mask & IN_OPEN)
|
||||||
inOpen(std::string(event->name));
|
inOpen(name.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#ifndef __INotify_h__
|
#ifndef __INotify_h__
|
||||||
# define __INotify_h__
|
# define __INotify_h__
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "Socket.h"
|
#include "Socket.h"
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
#include "IPAddress.h"
|
#include "IPAddress.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#ifndef __IPAddress_h__
|
#ifndef __IPAddress_h__
|
||||||
#define __IPAddress_h__
|
#define __IPAddress_h__
|
||||||
|
|
||||||
#include "includes"
|
#include <sys/socket.h>
|
||||||
#include "Object.h"
|
#include <netinet/in.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
class IPAddress : public Object {
|
class IPAddress {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IPAddress();
|
IPAddress();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#ifndef __IPAddressList_h__
|
#ifndef __IPAddressList_h__
|
||||||
#define __IPAddressList_h__
|
#define __IPAddressList_h__
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "IPAddress.h"
|
#include "IPAddress.h"
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
#
|
|
||||||
# 'make' build executable file 'main'
|
|
||||||
# 'make clean' removes all .o and executable files
|
|
||||||
#
|
|
||||||
|
|
||||||
# define the Cpp compiler to use
|
|
||||||
CXX = g++
|
|
||||||
|
|
||||||
# define any compile-time flags
|
|
||||||
CXXFLAGS := -std=c++17 -Wall -Wextra -g
|
|
||||||
|
|
||||||
# define library paths in addition to /usr/lib
|
|
||||||
# if I wanted to include libraries not in /usr/lib I'd specify
|
|
||||||
# their path using -Lpath, something like:
|
|
||||||
LFLAGS =
|
|
||||||
|
|
||||||
# define output directory
|
|
||||||
OUTPUT := output
|
|
||||||
|
|
||||||
# define source directory
|
|
||||||
SRC := src
|
|
||||||
|
|
||||||
# define include directory
|
|
||||||
INCLUDE := include
|
|
||||||
|
|
||||||
# define lib directory
|
|
||||||
LIB := lib
|
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
MAIN := main.exe
|
|
||||||
SOURCEDIRS := $(SRC)
|
|
||||||
INCLUDEDIRS := $(INCLUDE)
|
|
||||||
LIBDIRS := $(LIB)
|
|
||||||
FIXPATH = $(subst /,\,$1)
|
|
||||||
RM := del /q /f
|
|
||||||
MD := mkdir
|
|
||||||
else
|
|
||||||
MAIN := main
|
|
||||||
SOURCEDIRS := $(shell find $(SRC) -type d)
|
|
||||||
INCLUDEDIRS := $(shell find $(INCLUDE) -type d)
|
|
||||||
LIBDIRS := $(shell find $(LIB) -type d)
|
|
||||||
FIXPATH = $1
|
|
||||||
RM = rm -f
|
|
||||||
MD := mkdir -p
|
|
||||||
endif
|
|
||||||
|
|
||||||
# define any directories containing header files other than /usr/include
|
|
||||||
INCLUDES := $(patsubst %,-I%, $(INCLUDEDIRS:%/=%))
|
|
||||||
|
|
||||||
# define the C libs
|
|
||||||
LIBS := $(patsubst %,-L%, $(LIBDIRS:%/=%))
|
|
||||||
|
|
||||||
# define the C source files
|
|
||||||
SOURCES := $(wildcard $(patsubst %,%/*.cpp, $(SOURCEDIRS)))
|
|
||||||
|
|
||||||
# define the C object files
|
|
||||||
OBJECTS := $(SOURCES:.cpp=.o)
|
|
||||||
|
|
||||||
#
|
|
||||||
# The following part of the makefile is generic; it can be used to
|
|
||||||
# build any executable just by changing the definitions above and by
|
|
||||||
# deleting dependencies appended to the file from 'make depend'
|
|
||||||
#
|
|
||||||
|
|
||||||
OUTPUTMAIN := $(call FIXPATH,$(OUTPUT)/$(MAIN))
|
|
||||||
|
|
||||||
all: $(OUTPUT) $(MAIN)
|
|
||||||
@echo Executing 'all' complete!
|
|
||||||
|
|
||||||
$(OUTPUT):
|
|
||||||
$(MD) $(OUTPUT)
|
|
||||||
|
|
||||||
$(MAIN): $(OBJECTS)
|
|
||||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $(OUTPUTMAIN) $(OBJECTS) $(LFLAGS) $(LIBS)
|
|
||||||
|
|
||||||
# this is a suffix replacement rule for building .o's from .c's
|
|
||||||
# it uses automatic variables $<: the name of the prerequisite of
|
|
||||||
# the rule(a .c file) and $@: the name of the target of the rule (a .o file)
|
|
||||||
# (see the gnu make manual section about automatic variables)
|
|
||||||
.cpp.o:
|
|
||||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
$(RM) $(OUTPUTMAIN)
|
|
||||||
$(RM) $(call FIXPATH,$(OBJECTS))
|
|
||||||
@echo Cleanup complete!
|
|
||||||
|
|
||||||
run: all
|
|
||||||
./$(OUTPUTMAIN)
|
|
||||||
@echo Executing 'run: all' complete!
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#ifndef __Object_h__
|
|
||||||
#define __Object_h__
|
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
class Object {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
std::string tag;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,20 +1,14 @@
|
|||||||
#ifndef __SessionFilter_h__
|
#ifndef __SessionFilter_h__
|
||||||
#define __SessionFilter_h__
|
#define __SessionFilter_h__
|
||||||
|
|
||||||
//#include "Session.h"
|
namespace core {
|
||||||
#include "Object.h"
|
|
||||||
|
|
||||||
namespace core
|
|
||||||
{
|
|
||||||
|
|
||||||
class TCPSession;
|
class TCPSession;
|
||||||
|
|
||||||
class SessionFilter : public Object
|
class SessionFilter {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual bool test(TCPSession &session)
|
virtual bool test(TCPSession &session) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Exception.h"
|
#include "Exception.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "ZString.h"
|
#include "ZString.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace core
|
namespace core
|
||||||
{
|
{
|
||||||
@@ -68,36 +69,105 @@ namespace core
|
|||||||
void Socket::onUnregistered() {}
|
void Socket::onUnregistered() {}
|
||||||
|
|
||||||
bool Socket::eventReceived(struct epoll_event event, long long eventId) {
|
bool Socket::eventReceived(struct epoll_event event, long long eventId) {
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor();
|
coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process beginning for socket " << getDescriptor();
|
||||||
if(inHandler)
|
if(inHandler)
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true.";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "inHandler was already true.";
|
||||||
inHandler = true;
|
inHandler = true;
|
||||||
if(event.events & EPOLLRDHUP) {
|
if(event.events & EPOLLRDHUP) {
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLRDHUP";
|
||||||
readHangup = true;
|
readHangup = true;
|
||||||
shutdown("hangup received");
|
shutdown("hangup received");
|
||||||
}
|
}
|
||||||
if(event.events & EPOLLIN) {
|
if(event.events & EPOLLIN) {
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLIN";
|
||||||
|
// A read just happened (or at least an opportunity for OpenSSL
|
||||||
|
// to make progress on whatever it needed a read for) - give any
|
||||||
|
// write that was waiting on that a fresh attempt via this same
|
||||||
|
// call's own trailing resetSocket(), rather than leaving
|
||||||
|
// EPOLLWRNORM permanently un-armed from a single earlier stall.
|
||||||
|
waitingOnReadToWrite = false;
|
||||||
coreutils::ZString zbuffer(buffer, length);
|
coreutils::ZString zbuffer(buffer, length);
|
||||||
lock.lock();
|
// 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);
|
receiveData(zbuffer);
|
||||||
if(!shutDown) {
|
} catch(coreutils::Exception &e) {
|
||||||
inHandler = false;
|
// Also to stderr, not just Log() - this process's Log class
|
||||||
lock.unlock();
|
// writes through a static listener/file neither this server
|
||||||
resetSocket();
|
// 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) {
|
if(event.events & EPOLLWRNORM) {
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "EPOLLWRNORM";
|
||||||
writeSocket();
|
writeSocket();
|
||||||
|
}
|
||||||
inHandler = false;
|
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();
|
resetSocket();
|
||||||
|
return keepAlive;
|
||||||
}
|
}
|
||||||
inHandler = false;
|
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_1) << "Event process ending for socket " << getDescriptor();
|
void Socket::connectionRequest() {}
|
||||||
return !shutDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Socket::onDataReceived(std::string data) {
|
void Socket::onDataReceived(std::string data) {
|
||||||
throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1);
|
throw coreutils::Exception("Need to override onDataReceived.", __FILE__, __LINE__, -1);
|
||||||
@@ -117,20 +187,15 @@ namespace core
|
|||||||
coreutils::ZString zbuffer(buffer.getData(), len);
|
coreutils::ZString zbuffer(buffer.getData(), len);
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer;
|
// coreutils::Log(coreutils::LOG_DEBUG_1) << zbuffer;
|
||||||
onDataReceived(zbuffer);
|
onDataReceived(zbuffer);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
error = errno;
|
error = errno;
|
||||||
|
switch (error) {
|
||||||
switch (error)
|
|
||||||
{
|
|
||||||
|
|
||||||
// When a listening socket receives a connection
|
// When a listening socket receives a connection
|
||||||
// request we get one of these.
|
// request we get one of these.
|
||||||
//
|
|
||||||
case ENOTCONN:
|
case ENOTCONN:
|
||||||
onDataReceived(blank);
|
connectionRequest();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ECONNRESET:
|
case ECONNRESET:
|
||||||
@@ -142,13 +207,74 @@ namespace core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Socket::sendData(const char *data, int length) {
|
||||||
|
return ::write(descriptor, data, length);
|
||||||
|
}
|
||||||
|
|
||||||
void Socket::writeSocket() {
|
void Socket::writeSocket() {
|
||||||
outlock.lock();
|
outlock.lock();
|
||||||
// coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor();
|
// coreutils::Log(coreutils::LOG_DEBUG_3) << "writing data to socket " << getDescriptor();
|
||||||
if(fifo.size() > 0) {
|
if(fifo.size() > 0) {
|
||||||
if(!shutDown)
|
// No longer gated on !shutDown: writeSocket() only ever runs in
|
||||||
int rc = ::write(descriptor, fifo.front().c_str(), fifo.front().length());
|
// 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;
|
||||||
|
waitingOnReadToWrite = false;
|
||||||
|
if((size_t)sent >= fifo.front().length())
|
||||||
fifo.pop();
|
fifo.pop();
|
||||||
|
else
|
||||||
|
fifo.front().erase(0, sent);
|
||||||
|
} else if(sendNeedsRead()) {
|
||||||
|
// Not a stall in the CPU-spinning sense consecutiveWriteStalls
|
||||||
|
// exists to catch - resetSocket() below simply won't re-arm
|
||||||
|
// EPOLLWRNORM while this is true, so there's nothing to busy-
|
||||||
|
// retry until a real EPOLLIN event (already always watched
|
||||||
|
// for) brings in whatever OpenSSL is actually waiting to read.
|
||||||
|
waitingOnReadToWrite = true;
|
||||||
|
} 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();
|
outlock.unlock();
|
||||||
}
|
}
|
||||||
@@ -195,9 +321,17 @@ namespace core
|
|||||||
struct epoll_event event;
|
struct epoll_event event;
|
||||||
event.data.ptr = this;
|
event.data.ptr = this;
|
||||||
event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET;
|
event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT | EPOLLET;
|
||||||
if(fifo.size() > 0)
|
if(fifo.size() > 0 && !waitingOnReadToWrite)
|
||||||
event.events |= EPOLLWRNORM;
|
event.events |= EPOLLWRNORM;
|
||||||
if(!shutDown)
|
// 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);
|
epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, getDescriptor(), &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
#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,9 +1,10 @@
|
|||||||
#ifndef __Socket_h__
|
#ifndef __Socket_h__
|
||||||
#define __Socket_h__
|
#define __Socket_h__
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "Object.h"
|
|
||||||
#include "ZString.h"
|
#include "ZString.h"
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -136,6 +137,13 @@ namespace core {
|
|||||||
|
|
||||||
// virtual void onConnected(); ///< Called when socket is open and ready to communicate.
|
// 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();
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@@ -165,6 +173,26 @@ namespace core {
|
|||||||
|
|
||||||
virtual void receiveData(coreutils::ZString &buffer);
|
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);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Overridden by TCPSocket to report whether the last sendData()
|
||||||
|
/// call failed specifically because the TLS layer needs a READ to
|
||||||
|
/// make progress (SSL_ERROR_WANT_READ), as opposed to any other
|
||||||
|
/// reason - see waitingOnReadToWrite's own doc comment for why
|
||||||
|
/// writeSocket()/resetSocket() need to know this. Always false for
|
||||||
|
/// a plain (non-TLS) socket, which has no such distinction.
|
||||||
|
///
|
||||||
|
|
||||||
|
virtual bool sendNeedsRead() { return false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
@@ -192,6 +220,36 @@ namespace core {
|
|||||||
|
|
||||||
std::queue<std::string> fifo;
|
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;
|
||||||
|
|
||||||
|
// Set by writeSocket() when the last failed send specifically
|
||||||
|
// needed a read to proceed (sendNeedsRead() true) rather than
|
||||||
|
// being genuinely stalled on write-readiness. resetSocket() skips
|
||||||
|
// adding EPOLLWRNORM to the re-armed event mask while this is
|
||||||
|
// true - re-arming it anyway just spins the CPU as fast as it can
|
||||||
|
// loop, since an already-writable TCP socket keeps firing
|
||||||
|
// EPOLLWRNORM immediately while the actual blocker (more bytes
|
||||||
|
// from the peer) has nothing to do with write-readiness at all.
|
||||||
|
// Cleared on any EPOLLIN event (see eventReceived()) so the write
|
||||||
|
// gets a fresh attempt via that same event's own trailing
|
||||||
|
// resetSocket() call, once there's real reason to expect progress.
|
||||||
|
bool waitingOnReadToWrite = false;
|
||||||
|
|
||||||
void resetSocket();
|
void resetSocket();
|
||||||
|
|
||||||
std::mutex lock;
|
std::mutex lock;
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "ZString.h"
|
#include "ZString.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace core {
|
|||||||
temp = "";
|
temp = "";
|
||||||
}
|
}
|
||||||
countSubscribed += subscription->unsubscribe(session);
|
countSubscribed += subscription->unsubscribe(session);
|
||||||
if (subscription->subscriptionOwners == session.alias) {
|
if (subscription->subscriptionOwners == coreutils::MString(session.alias)) {
|
||||||
if (subscription->selectionOfOwnerForSubscription == "*DIE") {
|
if (subscription->selectionOfOwnerForSubscription == "*DIE") {
|
||||||
onClearSubscription(temp, key);
|
onClearSubscription(temp, key);
|
||||||
}
|
}
|
||||||
@@ -93,8 +93,8 @@ namespace core {
|
|||||||
else if (request[0] == "invite") {
|
else if (request[0] == "invite") {
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_1) << request[2];
|
coreutils::Log(coreutils::LOG_DEBUG_1) << request[2];
|
||||||
invitee = (coreutils::MString *)&request[2];
|
std::string invitee = request[2].str();
|
||||||
TCPSession *tempSession = session.server.getSessionByAlias(invitee);
|
TCPSession *tempSession = session.server->getSessionByAlias(&invitee);
|
||||||
std::stringstream temp;
|
std::stringstream temp;
|
||||||
temp << "invite:" << request[1] << ":" << invitee;
|
temp << "invite:" << request[1] << ":" << invitee;
|
||||||
tempSession->write(temp.str());
|
tempSession->write(temp.str());
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ namespace core {
|
|||||||
std::map<coreutils::MString, Subscription *> subscriptions;
|
std::map<coreutils::MString, Subscription *> subscriptions;
|
||||||
Subscription *newSubscription;
|
Subscription *newSubscription;
|
||||||
std::mutex lock;
|
std::mutex lock;
|
||||||
coreutils::MString invitee;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,17 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
|
|
||||||
namespace core
|
namespace core {
|
||||||
{
|
|
||||||
|
|
||||||
TCPServer::TCPServer(EPoll &ePoll, IPAddress address, std::string delimiter, int depth, std::string text)
|
TCPServer::TCPServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo, std::string delimiter, int depth, std::string text)
|
||||||
: TCPSocket(ePoll, text), commands(delimiter, depth)
|
: TCPSocket(ePoll, tlsInfo, text), commands(delimiter, depth) {
|
||||||
{
|
|
||||||
|
|
||||||
|
// Wires the subscription (pub/sub) command set into this server's
|
||||||
|
// own command dispatch - previously missing entirely here (the
|
||||||
|
// subscriptions member existed but nothing ever routed these verbs
|
||||||
|
// to it), so publish/subscribe/catalog/etc were unreachable on any
|
||||||
|
// TCPServer built after the TLS work, even though SubscriptionManager
|
||||||
|
// itself was still fully implemented.
|
||||||
commands.add(subscriptions, "publish");
|
commands.add(subscriptions, "publish");
|
||||||
commands.add(subscriptions, "unpublish");
|
commands.add(subscriptions, "unpublish");
|
||||||
commands.add(subscriptions, "subscribe");
|
commands.add(subscriptions, "subscribe");
|
||||||
@@ -30,23 +34,41 @@ namespace core
|
|||||||
throw coreutils::Exception("Error on listen to socket");
|
throw coreutils::Exception("Error on listen to socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
TCPServer::~TCPServer()
|
TCPServer::~TCPServer() {
|
||||||
{
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_2) << "Closing server socket " << getDescriptor() << ".";
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "Closing server socket " << getDescriptor() << ".";
|
||||||
close(getDescriptor());
|
close(getDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::onDataReceived(std::string data)
|
void TCPServer::connectionRequest() {
|
||||||
{
|
coreutils::Log(coreutils::LOG_DEBUG_2) << "Connection request is being received on socket " << getDescriptor() << ".";
|
||||||
lock.lock();
|
lock.lock();
|
||||||
TCPSession *session = accept();
|
TCPSession *session = accept();
|
||||||
if (session)
|
if (session)
|
||||||
sessions.push_back(session);
|
sessions.push_back(session);
|
||||||
|
// Must run ON THE SESSION, not `this` (the listening server) - the
|
||||||
|
// unqualified registerSocket()/acceptSocket() calls this replaced
|
||||||
|
// built the SSL* handshake state on the SERVER's own TLS-base
|
||||||
|
// member instead of the newly accepted session's, leaving every
|
||||||
|
// session's own `ssl` uninitialized and segfaulting the first time
|
||||||
|
// real data arrived on it (SSL_read() against a never-SSL_new()'d
|
||||||
|
// pointer - confirmed via gdb). Sessions already correctly inherit
|
||||||
|
// this server's SSL_CTX at construction (TCPSession's own
|
||||||
|
// TLS(SSL_CTX*)-taking constructor) - performTLSHandshake() just
|
||||||
|
// needed to actually be invoked on that object.
|
||||||
|
if (session && tlsInfo)
|
||||||
|
session->performTLSHandshake();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
TCPSession *TCPServer::accept()
|
void TCPServer::receiveData(coreutils::ZString &buffer) {
|
||||||
{
|
// See this override's own doc comment in TCPServer.h - the
|
||||||
|
// listening socket always uses the plain read()/ENOTCONN detection
|
||||||
|
// path, never TLS::receiveData(), regardless of whether this server
|
||||||
|
// is TLS-configured.
|
||||||
|
Socket::receiveData(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSession * TCPServer::accept() {
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -65,19 +87,16 @@ namespace core
|
|||||||
// }
|
// }
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
catch (coreutils::Exception e)
|
catch (coreutils::Exception e) {
|
||||||
{
|
|
||||||
coreutils::Log(coreutils::LOG_EXCEPT) << "Major error on session initialization. Error is '" << e.text << "'.";
|
coreutils::Log(coreutils::LOG_EXCEPT) << "Major error on session initialization. Error is '" << e.text << "'.";
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...) {
|
||||||
{
|
|
||||||
coreutils::Log(coreutils::LOG_EXCEPT) << "Unnspecified error on session initialization.";
|
coreutils::Log(coreutils::LOG_EXCEPT) << "Unnspecified error on session initialization.";
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::removeFromSessionList(TCPSession *session)
|
void TCPServer::removeFromSessionList(TCPSession *session) {
|
||||||
{
|
|
||||||
std::vector<TCPSession *>::iterator cursor;
|
std::vector<TCPSession *>::iterator cursor;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
for (cursor = sessions.begin(); cursor < sessions.end(); ++cursor)
|
for (cursor = sessions.begin(); cursor < sessions.end(); ++cursor)
|
||||||
@@ -89,14 +108,12 @@ namespace core
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::sessionErrorHandler(std::string errorString, std::stringstream &out)
|
void TCPServer::sessionErrorHandler(std::string errorString, std::stringstream &out) {
|
||||||
{
|
|
||||||
throw coreutils::Exception(errorString);
|
throw coreutils::Exception(errorString);
|
||||||
}
|
}
|
||||||
|
|
||||||
TCPSession *TCPServer::getSocketAccept(EPoll &ePoll)
|
TCPSession * TCPServer::getSocketAccept(EPoll &ePoll) {
|
||||||
{
|
return new TCPSession(ePoll, this);
|
||||||
return new TCPSession(ePoll, *this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::output(std::stringstream &out)
|
void TCPServer::output(std::stringstream &out)
|
||||||
@@ -116,23 +133,20 @@ namespace core
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::sendToAll(std::stringstream &data)
|
void TCPServer::sendToAll(std::stringstream &data) {
|
||||||
{
|
|
||||||
for (auto session : sessions)
|
for (auto session : sessions)
|
||||||
session->write(data.str());
|
session->write(data.str());
|
||||||
data.str("");
|
data.str("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender)
|
void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender) {
|
||||||
{
|
|
||||||
for (auto session : sessions)
|
for (auto session : sessions)
|
||||||
if (session != &sender)
|
if (session != &sender)
|
||||||
session->write(data.str());
|
session->write(data.str());
|
||||||
data.str("");
|
data.str("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender, SessionFilter filter)
|
void TCPServer::sendToAll(std::stringstream &data, TCPSession &sender, SessionFilter filter) {
|
||||||
{
|
|
||||||
for (auto session : sessions)
|
for (auto session : sessions)
|
||||||
if (filter.test(*session))
|
if (filter.test(*session))
|
||||||
if (session != &sender)
|
if (session != &sender)
|
||||||
@@ -140,7 +154,8 @@ namespace core
|
|||||||
data.str("");
|
data.str("");
|
||||||
}
|
}
|
||||||
|
|
||||||
TCPSession *TCPServer::getSessionByAlias(coreutils::MString alias) {
|
TCPSession *TCPServer::getSessionByAlias(void *alias) {
|
||||||
|
coreutils::Log(coreutils::LOG_DEBUG_1) << alias;
|
||||||
for (auto session : sessions)
|
for (auto session : sessions)
|
||||||
if (session->alias == alias)
|
if (session->alias == alias)
|
||||||
return session;
|
return session;
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
# include "SubscriptionManager.h"
|
# include "SubscriptionManager.h"
|
||||||
# include "TCPSession.h"
|
# include "TCPSession.h"
|
||||||
# include "TCPSocket.h"
|
# include "TCPSocket.h"
|
||||||
|
# include "TLSInfo.h"
|
||||||
|
|
||||||
namespace core
|
namespace core {
|
||||||
{
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// TCPServer
|
/// TCPServer
|
||||||
@@ -20,14 +20,14 @@ namespace core
|
|||||||
///
|
///
|
||||||
/// A list of connections is maintained in a vector object.
|
/// A list of connections is maintained in a vector object.
|
||||||
///
|
///
|
||||||
/// This object extends the BMACommand object as well so it can be added to a Console object and
|
/// This object extends the Command object as well so it can be added to a Console object and
|
||||||
/// process commands to display status information.
|
/// process commands to display status information.
|
||||||
///
|
///
|
||||||
|
|
||||||
class TCPServer : public TCPSocket, public Command
|
class TCPServer : public TCPSocket, public Command {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
/// The constructor for the TCPServer object.
|
/// The constructor for the TCPServer object.
|
||||||
///
|
///
|
||||||
@@ -37,7 +37,7 @@ namespace core
|
|||||||
/// @param commandName the name of the command used to invoke the status display for this object.
|
/// @param commandName the name of the command used to invoke the status display for this object.
|
||||||
///
|
///
|
||||||
|
|
||||||
TCPServer(EPoll &ePoll, IPAddress address, std::string delimiter = " ", int depth = 10, std::string text = "");
|
TCPServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo, std::string delimiter = " ", int depth = 10, std::string text = "");
|
||||||
|
|
||||||
///
|
///
|
||||||
/// The destructor for this object.
|
/// The destructor for this object.
|
||||||
@@ -122,20 +122,33 @@ namespace core
|
|||||||
/// of the alias pointer.
|
/// of the alias pointer.
|
||||||
///
|
///
|
||||||
|
|
||||||
TCPSession *getSessionByAlias(coreutils::MString alias);
|
TCPSession *getSessionByAlias(void *alias);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
///
|
///
|
||||||
/// Override the virtual dataReceived since for the server these
|
/// This method is called by the lower socket when a connection request comes into
|
||||||
/// are requests to accept the new connection socket.
|
/// the listening and bound server port.
|
||||||
/// No data is to be read or written when this method is called. It is the response to
|
|
||||||
/// the fact that a new connection is coming into the system
|
|
||||||
///
|
|
||||||
/// @param data the pointer to the buffer containing the received data.
|
|
||||||
/// @param length the length of the associated data buffer.
|
|
||||||
///
|
///
|
||||||
|
|
||||||
void onDataReceived(std::string data) override;
|
void connectionRequest() override;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// The listening socket itself is never a TLS peer - accepting a
|
||||||
|
/// connection on it surfaces as a plain read() failing with ENOTCONN
|
||||||
|
/// (see Socket::receiveData(), which is what actually detects and
|
||||||
|
/// dispatches to connectionRequest() above). When this server is
|
||||||
|
/// TLS-configured, TCPSocket::receiveData()'s normal ctx-based
|
||||||
|
/// dispatch would instead route that same event through
|
||||||
|
/// TLS::receiveData()/SSL_read() against a listening fd that was
|
||||||
|
/// never SSL_accept()-ed at all (real per-connection sessions are
|
||||||
|
/// the only sockets TLS applies to) - confirmed via a real crash
|
||||||
|
/// (SIGSEGV inside SSL_read, called with `this` pointing at this
|
||||||
|
/// server object, not a session). Always using the plain path here,
|
||||||
|
/// regardless of whether tlsInfo is set, keeps connection-detection
|
||||||
|
/// working the same way with or without TLS configured.
|
||||||
|
///
|
||||||
|
|
||||||
|
void receiveData(coreutils::ZString &buffer) override;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// This method is called when the Command associated with this object is requested
|
/// This method is called when the Command associated with this object is requested
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
#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
|
||||||
@@ -4,18 +4,40 @@
|
|||||||
#include "TCPServer.h"
|
#include "TCPServer.h"
|
||||||
#include "uuid/uuid.h"
|
#include "uuid/uuid.h"
|
||||||
|
|
||||||
namespace core
|
namespace core {
|
||||||
{
|
|
||||||
|
|
||||||
TCPSession::TCPSession(EPoll &ePoll, TCPServer &server, std::string text) : TCPSocket(ePoll, text), server(server) {
|
TCPSession::TCPSession(EPoll &ePoll, std::string text) : TCPSocket(ePoll, text) {
|
||||||
uuid_t uuid;
|
// uuid_t is 16 raw binary bytes, not a NUL-terminated string -
|
||||||
uuid_generate(uuid);
|
// streaming it directly (the old code did `<< uuidObject`) hits
|
||||||
alias = coreutils::MString((char *)uuid, 26);
|
// libstdc++'s unsigned-char* operator<< overload, which treats its
|
||||||
|
// argument as a C-string and calls strlen() on it: a real
|
||||||
|
// stack-buffer-overflow read past the 16-byte local, confirmed via
|
||||||
|
// AddressSanitizer under concurrent load. Separately, `alias` was
|
||||||
|
// being pointed at `uuidObject` itself - a stack-local array that is
|
||||||
|
// destroyed the instant this constructor returns, leaving `alias` a
|
||||||
|
// dangling pointer for the rest of the session's life (used by
|
||||||
|
// compareAlias() for session identity). uuid_unparse() into the
|
||||||
|
// `uuid` member (declared for exactly this - see its doc comment)
|
||||||
|
// fixes both: a real NUL-terminated string safe to log, backed by
|
||||||
|
// storage that lives as long as the TCPSession does.
|
||||||
|
uuid_t uuidObject;
|
||||||
|
uuid_generate(uuidObject);
|
||||||
|
uuid_unparse(uuidObject, uuid);
|
||||||
|
coreutils::Log(coreutils::LOG_DEBUG_1) << uuid;
|
||||||
|
alias = (void *)uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSession::TCPSession(EPoll &ePoll, TCPServer *server, std::string text) : TCPSocket(ePoll, server->ctx, text), server(server) {
|
||||||
|
uuid_t uuidObject;
|
||||||
|
uuid_generate(uuidObject);
|
||||||
|
uuid_unparse(uuidObject, uuid);
|
||||||
|
coreutils::Log(coreutils::LOG_DEBUG_1) << uuid;
|
||||||
|
alias = (void *)uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
TCPSession::~TCPSession() {
|
TCPSession::~TCPSession() {
|
||||||
server.removeFromSessionList(this);
|
server->removeFromSessionList(this);
|
||||||
server.subscriptions.removeSessionSubscriptions(*this);
|
server->subscriptions.removeSessionSubscriptions(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPSession::output(std::stringstream &data) {
|
void TCPSession::output(std::stringstream &data) {
|
||||||
@@ -23,14 +45,34 @@ namespace core
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TCPSession::protocol(coreutils::ZString &data) {
|
void TCPSession::protocol(coreutils::ZString &data) {
|
||||||
if (data.getLength() != 0)
|
// The data.getLength() != 0 guard alone silently swallowed every
|
||||||
if (!server.commands.processRequest(data, *this))
|
// blank line a client sent, before it ever reached
|
||||||
|
// CommandList::processRequest() - fine for ordinary command
|
||||||
|
// dispatch (a stray blank line between commands isn't a command),
|
||||||
|
// but CommandList::processRequest() already special-cases
|
||||||
|
// session.grab != NULL to hand the line to the grabbed handler
|
||||||
|
// unconditionally, blank or not - a handler mid-grabInput() (e.g.
|
||||||
|
// __SMTP_DATA reading a message body, or an IMAP APPEND literal)
|
||||||
|
// depends on seeing blank lines as real content, not commands.
|
||||||
|
// With the outer guard unconditional, every message body's
|
||||||
|
// header/body separator - and every blank line within the body
|
||||||
|
// itself - was getting dropped before that handler ever saw it.
|
||||||
|
// Confirmed via a delivered test message with zero CRLFCRLF
|
||||||
|
// sequences anywhere in it, which is what first exposed this.
|
||||||
|
if (grab != NULL || data.getLength() != 0) {
|
||||||
|
if (!server->commands.processRequest(data, *this)) {
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_1) << "Received data could not be parsed: " << data.str();
|
coreutils::Log(coreutils::LOG_DEBUG_1) << "Received data could not be parsed: " << data.str();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bool TCPSession::compareAlias(coreutils::MString alias) {
|
bool TCPSession::compareAlias(void *alias) {
|
||||||
// return this->alias == alias;
|
return this->alias = alias;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
void TCPSession::outputAlias(std::stringstream &out) {
|
||||||
|
out << alias;
|
||||||
|
}
|
||||||
|
|
||||||
void TCPSession::onRegistered() {
|
void TCPSession::onRegistered() {
|
||||||
onConnected();
|
onConnected();
|
||||||
@@ -41,43 +83,6 @@ namespace core
|
|||||||
|
|
||||||
void TCPSession::onConnected() {}
|
void TCPSession::onConnected() {}
|
||||||
|
|
||||||
void TCPSession::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 TCPSession::setBlockSize(int blockSize) {
|
|
||||||
this->blockSize = blockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession::onLineReceived(coreutils::ZString &line) {
|
void TCPSession::onLineReceived(coreutils::ZString &line) {
|
||||||
protocol(line);
|
protocol(line);
|
||||||
send();
|
send();
|
||||||
@@ -85,12 +90,6 @@ namespace core
|
|||||||
shutdown("termination requested");
|
shutdown("termination requested");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPSession::onBlockReceived(coreutils::ZString &block) {
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_3) << "[" << block.getLength() << "]";
|
|
||||||
if (term)
|
|
||||||
shutdown("termination requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession::send() {
|
void TCPSession::send() {
|
||||||
if(out.tellp() > 0)
|
if(out.tellp() > 0)
|
||||||
write(out.str());
|
write(out.str());
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef __Session_h__
|
#ifndef __Session_h__
|
||||||
# define __Session_h__
|
# define __Session_h__
|
||||||
|
|
||||||
#include "MString.h"
|
|
||||||
# include "SessionFilter.h"
|
# include "SessionFilter.h"
|
||||||
# include "TCPSocket.h"
|
# include "TCPSocket.h"
|
||||||
namespace core
|
# include <sstream>
|
||||||
{
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
class Command;
|
class Command;
|
||||||
class TCPServer;
|
class TCPServer;
|
||||||
@@ -22,15 +22,21 @@ namespace core
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|
||||||
class TCPSession : public TCPSocket
|
class TCPSession : public TCPSocket {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|
||||||
TCPSession(EPoll &ePoll, TCPServer &server, std::string text = "");
|
TCPSession(EPoll &ePoll, std::string text = "");
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
TCPSession(EPoll &ePoll, TCPServer *server, std::string text = "");
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@@ -56,10 +62,11 @@ namespace core
|
|||||||
void terminate();
|
void terminate();
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
/// This is a pointer to the server that was used to 'spawn' the server connection.
|
||||||
|
/// If the session is just client side connection this pointer should be NULL;
|
||||||
///
|
///
|
||||||
|
|
||||||
TCPServer &server;
|
TCPServer *server;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Use out to send data to the session socket or other session sockets.
|
/// Use out to send data to the session socket or other session sockets.
|
||||||
@@ -76,20 +83,20 @@ namespace core
|
|||||||
char uuid[37];
|
char uuid[37];
|
||||||
|
|
||||||
///
|
///
|
||||||
/// alias is a MString that can be set to any string of characters that identifies
|
/// alias is a void pointer that can be set to point to any object that identifies
|
||||||
/// this session uniquely. Alias's should be unique between sessions.
|
/// this session uniquely. Using this approach, inheriting objects can determine
|
||||||
|
/// how it knows the contacts that this server manages.
|
||||||
///
|
///
|
||||||
|
|
||||||
coreutils::MString alias;
|
void *alias;
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|
||||||
// bool compareAlias(coreutils::MString alias);
|
virtual bool compareAlias(void *alias);
|
||||||
|
|
||||||
|
virtual void outputAlias(std::stringstream &out);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
///
|
///
|
||||||
@@ -98,13 +105,6 @@ namespace core
|
|||||||
|
|
||||||
virtual void onRegistered() override;
|
virtual void onRegistered() override;
|
||||||
|
|
||||||
///
|
|
||||||
/// 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
|
/// 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
|
/// represents a single line of data terminated by a LF or CRLF. If onDataReceived
|
||||||
@@ -112,16 +112,7 @@ namespace core
|
|||||||
/// this method explicitly using the class and member name.
|
/// this method explicitly using the class and member name.
|
||||||
///
|
///
|
||||||
|
|
||||||
virtual void onLineReceived(coreutils::ZString &line);
|
virtual void onLineReceived(coreutils::ZString &line) override;
|
||||||
|
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// This method is called from within the protocol method when protocol is called
|
/// This method is called from within the protocol method when protocol is called
|
||||||
@@ -143,22 +134,9 @@ namespace core
|
|||||||
|
|
||||||
virtual void protocol(coreutils::ZString &data);
|
virtual void protocol(coreutils::ZString &data);
|
||||||
|
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
char *lineBuffer = NULL;
|
|
||||||
int lineBufferSize = 0;
|
|
||||||
int lineLength = 0;
|
|
||||||
int blockLength = 0;
|
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
bool term = false;
|
|
||||||
int blockSize = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
#include "TCPSession2.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
#include "Log.h"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
TCPSession2::TCPSession2(EPoll &ePoll, std::string text) : TCPSocket(ePoll, text) {}
|
|
||||||
|
|
||||||
TCPSession2::~TCPSession2() {}
|
|
||||||
|
|
||||||
void TCPSession2::output(std::stringstream &data) {
|
|
||||||
data << "|" << ipAddress.getClientAddressAndPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::protocol(coreutils::ZString &data) {}
|
|
||||||
|
|
||||||
void TCPSession2::onRegistered() {
|
|
||||||
onConnected();
|
|
||||||
send();
|
|
||||||
if(term)
|
|
||||||
TCPSocket::shutdown("termination requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::onConnected() {}
|
|
||||||
|
|
||||||
void TCPSession2::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 TCPSession2::setBlockSize(int blockSize) {
|
|
||||||
this->blockSize = blockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::onLineReceived(coreutils::ZString &line) {
|
|
||||||
protocol(line);
|
|
||||||
send();
|
|
||||||
if(term)
|
|
||||||
TCPSocket::shutdown("termination requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::onBlockReceived(coreutils::ZString &block) {
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_3) << "[" << block.getLength() << "]";
|
|
||||||
if(term)
|
|
||||||
TCPSocket::shutdown("termination requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::send() {
|
|
||||||
if(out.tellp() > 0)
|
|
||||||
TCPSocket::write(out.str());
|
|
||||||
out.str("");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TCPSession2::terminate() {
|
|
||||||
term = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
#ifndef __TCPSession2_h__
|
|
||||||
# define __TCPSession2_h__
|
|
||||||
|
|
||||||
#include "TCPSocket.h"
|
|
||||||
#include "Timer.h"
|
|
||||||
#include "SessionFilter.h"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
class Command;
|
|
||||||
class TCPServer;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// TCPSession2
|
|
||||||
///
|
|
||||||
/// TCPSession defines the nature of the interaction with the client
|
|
||||||
/// and stores persistent data for a defined session. TCPSession objects
|
|
||||||
/// are not sockets but instead provide a communications control
|
|
||||||
/// mechanism. Protocol conversations are provided through extensions
|
|
||||||
/// from this object.
|
|
||||||
///
|
|
||||||
/// TCPSession2 is designed to be 'connected' instead of being served
|
|
||||||
/// by a server.
|
|
||||||
///
|
|
||||||
|
|
||||||
class TCPSession2 : public TCPSocket {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
TCPSession2(EPoll &ePoll, std::string text = "");
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual ~TCPSession2();
|
|
||||||
|
|
||||||
Command *grab = NULL;
|
|
||||||
|
|
||||||
virtual void output(std::stringstream &data);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The send method is used to output the contents of the out stream
|
|
||||||
/// to the session containing the stream.
|
|
||||||
///
|
|
||||||
|
|
||||||
void send();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Use this method to terminate this TCPSession.
|
|
||||||
///
|
|
||||||
|
|
||||||
void terminate();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Use out to send data to the session socket or other session sockets.
|
|
||||||
///
|
|
||||||
|
|
||||||
std::stringstream out;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onRegistered() override;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// This method is called from within the protocol method when protocol is called
|
|
||||||
/// on the initial connection where the data is an empty string. Use this method
|
|
||||||
/// to deliver a message to the connection upon connection.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void onConnected();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Override the protocol method to manage and control the session communications
|
|
||||||
/// in your inherited session. If you do not override this method then the Session
|
|
||||||
/// default will process the 'commands' added to the server object using the
|
|
||||||
/// processRequest method on the session input.
|
|
||||||
///
|
|
||||||
/// When data is received within the session two modes are available to pass the
|
|
||||||
/// data through the protocol method: LINE or BLOCK.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void protocol(coreutils::ZString &data);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
private:
|
|
||||||
char *lineBuffer = NULL;
|
|
||||||
int lineBufferSize = 0;
|
|
||||||
int lineLength = 0;
|
|
||||||
int blockLength = 0;
|
|
||||||
std::mutex mtx;
|
|
||||||
bool term = false;
|
|
||||||
int blockSize = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Exception.h"
|
#include "Exception.h"
|
||||||
#include "errno.h"
|
#include "errno.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -10,6 +12,10 @@ namespace core {
|
|||||||
|
|
||||||
TCPSocket::TCPSocket(EPoll &ePoll, std::string text) : Socket(ePoll, text) {}
|
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() {}
|
TCPSocket::~TCPSocket() {}
|
||||||
|
|
||||||
void TCPSocket::connect(IPAddress &address) {
|
void TCPSocket::connect(IPAddress &address) {
|
||||||
@@ -23,5 +29,161 @@ namespace core {
|
|||||||
out << "|" << ipAddress.getClientAddressAndPort();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TCPSocket::sendNeedsRead() {
|
||||||
|
return ctx && lastSendWantedRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#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,12 +1,13 @@
|
|||||||
|
|
||||||
#ifndef __TCPSocket_h__
|
#ifndef __TCPSocket_h__
|
||||||
#define __TCPSocket_h__
|
#define __TCPSocket_h__
|
||||||
|
|
||||||
#include "IPAddress.h"
|
|
||||||
#include "Socket.h"
|
#include "Socket.h"
|
||||||
#include "includes"
|
#include "TLS.h"
|
||||||
|
#include "TLSInfo.h"
|
||||||
|
#include "IPAddress.h"
|
||||||
|
|
||||||
namespace core
|
namespace core {
|
||||||
{
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// TCPSocket
|
/// TCPSocket
|
||||||
@@ -18,14 +19,44 @@ namespace core
|
|||||||
/// synchronous data connection.
|
/// synchronous data connection.
|
||||||
///
|
///
|
||||||
|
|
||||||
class TCPSocket : public Socket
|
class TCPSocket : public Socket, public TLS {
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
TCPSocket(EPoll &ePoll);
|
TCPSocket(EPoll &ePoll);
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
TCPSocket(EPoll &ePoll, std::string text);
|
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();
|
virtual ~TCPSocket();
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
void connect(IPAddress &address);
|
void connect(IPAddress &address);
|
||||||
|
|
||||||
IPAddress ipAddress;
|
IPAddress ipAddress;
|
||||||
@@ -33,11 +64,122 @@ namespace core
|
|||||||
///
|
///
|
||||||
/// The output method is called by a socket session (TCPSession) and
|
/// The output method is called by a socket session (TCPSession) and
|
||||||
/// will output the detail information for the client socket. When extending
|
/// will output the detail information for the client socket. When extending
|
||||||
/// BMATCPSocket or BMASession you can override the method to add attributes
|
/// TCPSocket or TCPSession you can override the method to add attributes
|
||||||
/// to the list.
|
/// to the list.
|
||||||
///
|
///
|
||||||
|
|
||||||
virtual void output(std::stringstream &out);
|
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;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// True only when this socket is actually TLS (ctx set) and the
|
||||||
|
/// last sendData() call failed specifically with
|
||||||
|
/// SSL_ERROR_WANT_READ - see TLS::lastSendWantedRead's own doc
|
||||||
|
/// comment for why Socket::writeSocket()/resetSocket() need this.
|
||||||
|
///
|
||||||
|
|
||||||
|
virtual bool sendNeedsRead() 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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
|
||||||
|
#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
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
|
||||||
|
#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
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
|
||||||
|
#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
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
#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;
|
||||||
|
lastSendWantedRead = false;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastSendWantedRead = false;
|
||||||
|
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.";
|
||||||
|
lastSendWantedRead = true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#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();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Set by sendData() when the last SSL_write() failed specifically
|
||||||
|
// with SSL_ERROR_WANT_READ (needs a READ to make progress - e.g.
|
||||||
|
// mid-handshake application data, or a post-handshake session
|
||||||
|
// ticket exchange) rather than any other reason. TCPSocket::
|
||||||
|
// sendNeedsRead() reads this (protected, so a subclass can reach
|
||||||
|
// it directly) to tell Socket::writeSocket()/resetSocket() not to
|
||||||
|
// busy-spin retrying the write via EPOLLWRNORM - see their own
|
||||||
|
// doc comments for the confirmed real damage (a 15GB+ debug log,
|
||||||
|
// and every affected connection dropped before ever reading the
|
||||||
|
// client's first command) this caused when unaddressed.
|
||||||
|
bool lastSendWantedRead = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
char *sip_cacert = (char *)"../testkeys/certs/pbxca.crt";
|
||||||
|
char *sip_cert = (char *)"../testkeys/certs/pbxserver.crt";
|
||||||
|
char *sip_key = (char *)"../testkeys/certs/pbxserver.key";
|
||||||
|
bool initialized = false;
|
||||||
|
SSL *ssl;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef __TLSInfo_h__
|
||||||
|
#define __TLSInfo_h__
|
||||||
|
|
||||||
|
#include "ZString.h"
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
///
|
||||||
|
/// TLSInfo
|
||||||
|
///
|
||||||
|
/// This object provides the support data to handle TLS on the server core and
|
||||||
|
/// session environment.
|
||||||
|
///
|
||||||
|
|
||||||
|
class TLSInfo {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
coreutils::ZString cACertificate;
|
||||||
|
coreutils::ZString certificate;
|
||||||
|
coreutils::ZString key;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#include "TLSServer.h"
|
|
||||||
#include "TLSSession.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "TCPSession.h"
|
|
||||||
#include "Exception.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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSServer::TLSServer(EPoll &ePoll, IPAddress address) : TCPServer(ePoll, address) {
|
|
||||||
|
|
||||||
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, sip_cert, SSL_FILETYPE_PEM) <= 0)
|
|
||||||
throw coreutils::Exception("Error looking up certificate.");
|
|
||||||
if(SSL_CTX_use_PrivateKey_file(ctx, sip_key, 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, sip_cacert, NULL))
|
|
||||||
throw coreutils::Exception("Cannot verify locations.");
|
|
||||||
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(sip_cacert));
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_1) << "Server key authenticated.";
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSServer::~TLSServer() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TCPSession * TLSServer::getSocketAccept() {
|
|
||||||
return new TLSSession(ePoll, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#ifndef TLSServerSocket_h__
|
|
||||||
#define TLSServerSocket_h__
|
|
||||||
|
|
||||||
#include "Socket.h"
|
|
||||||
#include "TCPServer.h"
|
|
||||||
#include "Command.h"
|
|
||||||
#include "TCPSession.h"
|
|
||||||
#include "IPAddress.h"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
///
|
|
||||||
/// TLSServer
|
|
||||||
///
|
|
||||||
/// Manage a socket connection as a TLS server type. Connections to the socket are processed through
|
|
||||||
/// the accept functionality.
|
|
||||||
///
|
|
||||||
|
|
||||||
class TLSServer : public TCPServer {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The constructor.
|
|
||||||
///
|
|
||||||
/// @param ePoll the BMAEPoll 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.
|
|
||||||
/// @return the instance of the BMATLSServerSocket.
|
|
||||||
|
|
||||||
TLSServer(EPoll &ePoll, IPAddress address);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The destructor for this object.
|
|
||||||
///
|
|
||||||
|
|
||||||
~TLSServer();
|
|
||||||
|
|
||||||
TCPSession * getSocketAccept();
|
|
||||||
|
|
||||||
SSL_CTX *ctx;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#include "TLSSession.h"
|
|
||||||
#include "EPoll.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "Exception.h"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
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.";
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSSession::TLSSession(EPoll &ePoll, TCPServer &server) : TCPSession(ePoll, server) {}
|
|
||||||
|
|
||||||
void TLSSession::onRegister() {
|
|
||||||
initialized = true;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_3) << "TLS socket initializing on socket " << getDescriptor() << "...";
|
|
||||||
|
|
||||||
fcntl(getDescriptor(), F_SETFL, fcntl(getDescriptor(), F_GETFL, 0) | O_NONBLOCK);
|
|
||||||
|
|
||||||
ssl = SSL_new(static_cast<TLSServer &>(server).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, getDescriptor())) == 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 TLSSession::onRegistered() {
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TLSSession::~TLSSession() {}
|
|
||||||
|
|
||||||
void TLSSession::protocol(coreutils::ZString &data) {}
|
|
||||||
|
|
||||||
void TLSSession::receiveData(coreutils::ZString &buffer) {
|
|
||||||
|
|
||||||
int len;
|
|
||||||
// int error = -1;
|
|
||||||
//
|
|
||||||
std::cout << "receiveData TLS" << std::endl;
|
|
||||||
|
|
||||||
if((len = ::SSL_read(ssl, buffer.getData(), buffer.getLength())) >= 0) {
|
|
||||||
std::cout << "receiveData TLS...len=" << len << ":" << buffer << std::endl;
|
|
||||||
onDataReceived(buffer);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void TLSSession::output(std::stringstream &out) {
|
|
||||||
out << "|" << ipAddress.getClientAddressAndPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#ifndef __TLSSession_h__
|
|
||||||
#define __TLSSession_h__
|
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "TCPSession.h"
|
|
||||||
#include "TLSServer.h"
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
class TLSServer;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// TLSSession
|
|
||||||
///
|
|
||||||
/// Provides a network TLS socket.
|
|
||||||
///
|
|
||||||
/// For accessing TLS network functions use this object. The connection oriented nature of TLS
|
|
||||||
/// provides a single client persistent connection with data error correction and a durable
|
|
||||||
/// synchronous data connection.
|
|
||||||
///
|
|
||||||
|
|
||||||
class TLSSession : public TCPSession {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
TLSSession(EPoll &ePoll, TCPServer &server);
|
|
||||||
~TLSSession();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The output method is called by a socket session (Session) and
|
|
||||||
/// will output the detail information for the client socket. When extending
|
|
||||||
/// TLSSocket or Session you can override the method to add attributes
|
|
||||||
/// to the list.
|
|
||||||
///
|
|
||||||
|
|
||||||
virtual void output(std::stringstream &out);
|
|
||||||
virtual void protocol(coreutils::ZString &data) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void receiveData(coreutils::ZString &buffer) override;
|
|
||||||
void onRegister();
|
|
||||||
void onRegistered();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initialized = false;
|
|
||||||
SSL *ssl;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
#include "TerminalSession.h"
|
#include "TerminalSession.h"
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
TerminalSession::TerminalSession(EPoll &ePoll, TCPServer &server) : TCPSession(ePoll, server) {
|
TerminalSession::TerminalSession(EPoll &ePoll, TCPServer *server) : TCPSession(ePoll, server) {}
|
||||||
}
|
|
||||||
|
|
||||||
TerminalSession::~TerminalSession() {
|
TerminalSession::~TerminalSession() {}
|
||||||
}
|
|
||||||
|
|
||||||
int TerminalSession::getLines() {
|
int TerminalSession::getLines() {
|
||||||
struct winsize size;
|
struct winsize size;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#ifndef __Terminal_h__
|
#ifndef __Terminal_h__
|
||||||
#define __Terminal_h__
|
#define __Terminal_h__
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
#include "TCPServer.h"
|
#include "TCPServer.h"
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ namespace core {
|
|||||||
class TerminalSession : public TCPSession {
|
class TerminalSession : public TCPSession {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TerminalSession(EPoll &ePoll, TCPServer &server);
|
TerminalSession(EPoll &ePoll, TCPServer *server);
|
||||||
~TerminalSession();
|
~TerminalSession();
|
||||||
|
|
||||||
int getLines();
|
int getLines();
|
||||||
|
|||||||
@@ -1,12 +1,38 @@
|
|||||||
#include "Thread.h"
|
#include "Thread.h"
|
||||||
#include "EPoll.h"
|
#include "EPoll.h"
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
Thread::Thread(EPoll &ePoll) : ePoll(ePoll) {}
|
Thread::Thread(EPoll &ePoll) : ePoll(ePoll), _thread(NULL), thread(NULL) {}
|
||||||
Thread::Thread(EPoll &ePoll, ThreadScope *scope) : ePoll(ePoll) {}
|
// Previously discarded `scope` entirely (never assigned to the `thread`
|
||||||
|
// member) - a real, live bug found while adding the first actual caller
|
||||||
|
// of this constructor (APIServer's MysqlThreadScope): the whole point
|
||||||
|
// of passing a scope in was to have it invoked from run() below, which
|
||||||
|
// silently never happened.
|
||||||
|
Thread::Thread(EPoll &ePoll, ThreadScope *scope) : ePoll(ePoll), _thread(NULL), thread(scope) {}
|
||||||
|
|
||||||
Thread::~Thread() {}
|
// Was empty - _thread (heap-allocated in start()) was never joined or
|
||||||
|
// freed here. A joinable std::thread destroyed without join()/detach()
|
||||||
|
// calls std::terminate() immediately, which this leaked-pointer
|
||||||
|
// approach silently sidestepped: the wrapped OS thread just kept
|
||||||
|
// running - polling this Thread's EPoll reference, touching Socket
|
||||||
|
// objects registered with it - even after this object, its EPoll, and
|
||||||
|
// every one of those sockets had already been destroyed out from under
|
||||||
|
// it (confirmed live: an exception thrown during BARANTMail startup,
|
||||||
|
// after EPoll::start() had already spun up its worker pool, unwound
|
||||||
|
// the stack destructing EPoll and its registered sockets while those
|
||||||
|
// still-running threads kept dereferencing them, producing a real
|
||||||
|
// "double free or corruption" crash). join() first (skipped if already
|
||||||
|
// joined via EPoll::stop() having run normally) so the OS thread is
|
||||||
|
// actually gone before anything it might touch gets freed.
|
||||||
|
Thread::~Thread() {
|
||||||
|
if (_thread) {
|
||||||
|
if (_thread->joinable())
|
||||||
|
_thread->join();
|
||||||
|
delete _thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Thread::start() {
|
void Thread::start() {
|
||||||
_thread = new std::thread(&Thread::run, this);
|
_thread = new std::thread(&Thread::run, this);
|
||||||
@@ -40,6 +66,9 @@ namespace core {
|
|||||||
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_1) << "Thread started with thread id " << threadId << ".";
|
coreutils::Log(coreutils::LOG_DEBUG_1) << "Thread started with thread id " << threadId << ".";
|
||||||
|
|
||||||
|
if(thread)
|
||||||
|
thread->onThreadStart();
|
||||||
|
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
struct epoll_event events[50];
|
struct epoll_event events[50];
|
||||||
@@ -50,13 +79,24 @@ namespace core {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
status = "WAITING";
|
status = "WAITING";
|
||||||
int rc = epoll_wait(ePoll.getDescriptor(), events, 50, -1);
|
// A finite timeout, not the previous -1 (wait forever), is what
|
||||||
|
// makes EPoll::stop() able to return at all: a thread idly
|
||||||
|
// blocked here with no socket activity would otherwise never
|
||||||
|
// re-check isStopping() above, so join()ing it (both
|
||||||
|
// EPoll::stop() and, now, ~Thread() itself) would hang forever.
|
||||||
|
// Costs up to a one-second shutdown latency.
|
||||||
|
int rc = epoll_wait(ePoll.getDescriptor(), events, 50, 1000);
|
||||||
status = "RUNNING";
|
status = "RUNNING";
|
||||||
|
|
||||||
if(rc < 0) {
|
if(rc < 0) {
|
||||||
// TODO: Make log entry indicating status received and ignore for now.
|
// TODO: Make log entry indicating status received and ignore for now.
|
||||||
} else if(rc == 0) {
|
} else if(rc == 0) {
|
||||||
break;
|
// Plain timeout, no socket activity - not itself a signal to
|
||||||
|
// stop (isStopping() above already decides that). Previously
|
||||||
|
// broke out of the loop unconditionally, which the old
|
||||||
|
// infinite wait never actually triggered in practice, but
|
||||||
|
// would have silently ended this thread after any single
|
||||||
|
// idle second once the wait became finite.
|
||||||
} else if(rc > 0) {
|
} else if(rc > 0) {
|
||||||
for(int ix = 0; ix < rc; ++ix) {
|
for(int ix = 0; ix < rc; ++ix) {
|
||||||
++count;
|
++count;
|
||||||
@@ -71,6 +111,10 @@ namespace core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(thread)
|
||||||
|
thread->onThreadEnd();
|
||||||
|
|
||||||
coreutils::Log(coreutils::LOG_DEBUG_1) << "Thread ending with thread id " << threadId << ".";
|
coreutils::Log(coreutils::LOG_DEBUG_1) << "Thread ending with thread id " << threadId << ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
#ifndef __Thread_h__
|
#ifndef __Thread_h__
|
||||||
#define __Thread_h__
|
#define __Thread_h__
|
||||||
|
|
||||||
#include "includes"
|
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Object.h"
|
|
||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
#include "ThreadScope.h"
|
#include "ThreadScope.h"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ namespace core {
|
|||||||
/// a Thread object for each thread specified in the EPoll's start method.
|
/// a Thread object for each thread specified in the EPoll's start method.
|
||||||
///
|
///
|
||||||
|
|
||||||
class Thread : public Object {
|
class Thread {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Thread(EPoll &ePoll);
|
Thread(EPoll &ePoll);
|
||||||
|
|||||||
@@ -3,10 +3,34 @@
|
|||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ThreadScope
|
||||||
|
///
|
||||||
|
/// Extension point for per-OS-thread setup/teardown in an EPoll worker
|
||||||
|
/// pool. EPoll's threads are long-lived (one persistent event loop per
|
||||||
|
/// thread, for the process's whole lifetime, not spawned per-request) -
|
||||||
|
/// onThreadStart() runs once at the top of each thread, before it
|
||||||
|
/// enters its event loop; onThreadEnd() runs once as that thread is
|
||||||
|
/// shutting down. A single ThreadScope instance is shared across every
|
||||||
|
/// worker thread (passed to EPoll::start()), so overrides should only
|
||||||
|
/// touch thread-LOCAL state from these hooks, never the ThreadScope
|
||||||
|
/// object's own fields, without their own synchronization.
|
||||||
|
///
|
||||||
|
/// Concrete motivating case: a C library that requires explicit
|
||||||
|
/// per-thread initialization before use from a multi-threaded program
|
||||||
|
/// (e.g. the MySQL client library's mysql_thread_init()/
|
||||||
|
/// mysql_thread_end() - see APIServer's MysqlThreadScope) has no other
|
||||||
|
/// clean way to hook into an EPoll worker thread's lifetime.
|
||||||
|
///
|
||||||
|
|
||||||
class ThreadScope {
|
class ThreadScope {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThreadScope() {}
|
ThreadScope() {}
|
||||||
|
virtual ~ThreadScope() {}
|
||||||
|
|
||||||
|
virtual void onThreadStart() {}
|
||||||
|
virtual void onThreadEnd() {}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
#include <sys/timerfd.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
namespace core
|
namespace core
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "UDPServerSocket.h"
|
#include "UDPServerSocket.h"
|
||||||
#include "EPoll.h"
|
#include "EPoll.h"
|
||||||
#include "TCPSession.h"
|
#include "TCPSession.h"
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n "Building library documentation manual..."
|
echo -n "Building library documentation manual..."
|
||||||
doxygen docs/latex/doxygen.sty >/dev/null 2>/dev/null
|
doxygen doxygen.sty >/dev/null 2>/dev/null
|
||||||
echo "OK"
|
echo "OK"
|
||||||
|
|
||||||
rm *.o
|
rm *.o
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAAccount.h Source File</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.13 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="menudata.js"></script>
|
|
||||||
<script type="text/javascript" src="menu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
initMenu('',true,false,'search.php','Search');
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div id="main-nav"></div>
|
|
||||||
</div><!-- top -->
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAAccount.h</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="preprocessor">#ifndef __BMAAccount_h__</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="preprocessor">#define __BMAAccount_h__</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> </div><div class="line"><a name="l00004"></a><span class="lineno"><a class="line" href="class_b_m_a_account.html"> 4</a></span> <span class="keyword">class </span><a class="code" href="class_b_m_a_account.html">BMAAccount</a> : <span class="keyword">public</span> <a class="code" href="class_b_m_a_object.html">BMAObject</a> {</div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> </div><div class="line"><a name="l00006"></a><span class="lineno"> 6</span>  <span class="keyword">public</span>:</div><div class="line"><a name="l00007"></a><span class="lineno"> 7</span>  <a class="code" href="class_b_m_a_account.html">BMAAccount</a>();</div><div class="line"><a name="l00008"></a><span class="lineno"> 8</span>  ~<a class="code" href="class_b_m_a_account.html">BMAAccount</a>();</div><div class="line"><a name="l00009"></a><span class="lineno"> 9</span>  </div><div class="line"><a name="l00010"></a><span class="lineno"> 10</span>  <span class="keywordtype">string</span> getName();</div><div class="line"><a name="l00011"></a><span class="lineno"> 11</span>  <span class="keywordtype">void</span> setName(<span class="keywordtype">string</span> name);</div><div class="line"><a name="l00012"></a><span class="lineno"> 12</span>  <span class="keywordtype">string</span> getAlias();</div><div class="line"><a name="l00013"></a><span class="lineno"> 13</span>  <span class="keywordtype">void</span> setAlias(<span class="keywordtype">string</span> name);</div><div class="line"><a name="l00014"></a><span class="lineno"> 14</span>  vector<string> contacts;</div><div class="line"><a name="l00015"></a><span class="lineno"> 15</span>  ```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` </div><div class="line"><a name="l00016"></a><span class="lineno"> 16</span>  </div><div class="line"><a name="l00017"></a><span class="lineno"> 17</span> };</div><div class="line"><a name="l00018"></a><span class="lineno"> 18</span> </div><div class="line"><a name="l00019"></a><span class="lineno"> 19</span> <span class="preprocessor">#endif</span></div><div class="ttc" id="class_b_m_a_account_html"><div class="ttname"><a href="class_b_m_a_account.html">BMAAccount</a></div><div class="ttdef"><b>Definition:</b> BMAAccount.h:4</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_object_html"><div class="ttname"><a href="class_b_m_a_object.html">BMAObject</a></div><div class="ttdef"><b>Definition:</b> BMAObject.h:6</div></div>
|
|
||||||
</div><!-- fragment --></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.13
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAAuthenticate.cpp File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAAuthenticate.cpp File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "<a class="el" href="_b_m_a_authenticate_8h_source.html">BMAAuthenticate.h</a>"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAAuthenticate.cpp:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_authenticate_8cpp__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8cpp" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8cpp" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8cpp">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_authenticate_8h.html" title="BMAAuthenticate.h" alt="" coords="227,109,362,136"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="210,184,378,211"/>
|
|
||||||
<area shape="rect" id="node4" href="_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="140,333,243,360"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="229,259,360,285"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="332,333,502,360"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="25,408,121,435"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="146,408,256,435"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.cpp" name="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.cpp">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_authenticate_8h.html" title="BMAAuthenticate.h" alt="" coords="227,109,362,136"/>
|
|
||||||
<area shape="rect" id="node3" href="$_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="210,184,378,211"/>
|
|
||||||
<area shape="rect" id="node4" href="$_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="140,333,243,360"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="229,259,360,285"/>
|
|
||||||
<area shape="rect" id="node9" href="$_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="332,333,502,360"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="25,408,121,435"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="146,408,256,435"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
4516a389b39c60e140f783cc3824bd1d
|
|
||||||
|
Before Width: | Height: | Size: 96 KiB |
@@ -1,127 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAAuthenticate.h File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="summary">
|
|
||||||
<a href="#nested-classes">Classes</a> </div>
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAAuthenticate.h File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "<a class="el" href="_b_m_a_t_c_p_server_socket_8h_source.html">BMATCPServerSocket.h</a>"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAAuthenticate.h:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_authenticate_8h__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8h" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8h" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8h">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="210,109,378,136"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="140,259,243,285"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="229,184,360,211"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="332,259,502,285"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="25,333,121,360"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="146,333,256,360"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
This graph shows which files directly or indirectly include this file:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_authenticate_8h__dep__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8hdep" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8hdep" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_authenticate_8hdep">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_authenticate_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAAuthenticate.cpp" alt="" coords="5,109,189,165"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p><a href="_b_m_a_authenticate_8h_source.html">Go to the source code of this file.</a></p>
|
|
||||||
<table class="memberdecls">
|
|
||||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a>
|
|
||||||
Classes</h2></td></tr>
|
|
||||||
<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_b_m_a_authenticate.html">BMAAuthenticate</a></td></tr>
|
|
||||||
<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr>
|
|
||||||
</table>
|
|
||||||
</div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.h" name="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.h">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_authenticate_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAAuthenticate.cpp" alt="" coords="5,109,189,165"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
6990c5da0ea71ff0fb49e734e508f2ce
|
|
||||||
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -1,8 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.h" name="/home/barant/Documents/Development/BMASockets/BMAAuthenticate.h">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="210,109,378,136"/>
|
|
||||||
<area shape="rect" id="node3" href="$_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="140,259,243,285"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="229,184,360,211"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="332,259,502,285"/>
|
|
||||||
<area shape="rect" id="node5" href="$_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="25,333,121,360"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="146,333,256,360"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
47e2cc4566474eae16b06bb68e488e3b
|
|
||||||
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,76 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Development/BMA/server_core/ServerCore/BMAAuthenticate.h Source File</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.13 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="menudata.js"></script>
|
|
||||||
<script type="text/javascript" src="menu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
initMenu('',true,false,'search.php','Search');
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div id="main-nav"></div>
|
|
||||||
</div><!-- top -->
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Development/BMA/server_core/ServerCore/BMAAuthenticate.h</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="preprocessor">#ifndef __BMAAuthenticate_h__</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="preprocessor">#define __BMAAuthenticate_h__</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> </div><div class="line"><a name="l00004"></a><span class="lineno"> 4</span> <span class="preprocessor">#include "includes"</span></div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> <span class="preprocessor">#include "BMASession.h"</span></div><div class="line"><a name="l00006"></a><span class="lineno"> 6</span> </div><div class="line"><a name="l00007"></a><span class="lineno"><a class="line" href="class_b_m_a_authenticate.html"> 7</a></span> <span class="keyword">class </span><a class="code" href="class_b_m_a_authenticate.html">BMAAuthenticate</a> : <span class="keyword">public</span> <a class="code" href="class_b_m_a_object.html">BMAObject</a> {</div><div class="line"><a name="l00008"></a><span class="lineno"> 8</span> </div><div class="line"><a name="l00009"></a><span class="lineno"> 9</span>  <span class="keyword">public</span>:</div><div class="line"><a name="l00010"></a><span class="lineno"> 10</span>  <a class="code" href="class_b_m_a_authenticate.html">BMAAuthenticate</a>(<a class="code" href="class_b_m_a_session.html">BMASession</a> *session);</div><div class="line"><a name="l00011"></a><span class="lineno"> 11</span>  ~<a class="code" href="class_b_m_a_authenticate.html">BMAAuthenticate</a>();</div><div class="line"><a name="l00012"></a><span class="lineno"> 12</span> </div><div class="line"><a name="l00013"></a><span class="lineno"> 13</span>  <span class="keywordtype">void</span> onStart();</div><div class="line"><a name="l00014"></a><span class="lineno"> 14</span>  <span class="keywordtype">void</span> onDataReceived(<span class="keywordtype">char</span> *data, <span class="keywordtype">int</span> length);</div><div class="line"><a name="l00015"></a><span class="lineno"> 15</span>  <span class="keywordtype">void</span> onEnd();</div><div class="line"><a name="l00016"></a><span class="lineno"> 16</span>  </div><div class="line"><a name="l00017"></a><span class="lineno"> 17</span> };</div><div class="line"><a name="l00018"></a><span class="lineno"> 18</span> </div><div class="line"><a name="l00019"></a><span class="lineno"> 19</span> <span class="preprocessor">#endif</span></div><div class="ttc" id="class_b_m_a_session_html"><div class="ttname"><a href="class_b_m_a_session.html">BMASession</a></div><div class="ttdef"><b>Definition:</b> BMASession.h:18</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_object_html"><div class="ttname"><a href="class_b_m_a_object.html">BMAObject</a></div><div class="ttdef"><b>Definition:</b> BMAObject.h:6</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_authenticate_html"><div class="ttname"><a href="class_b_m_a_authenticate.html">BMAAuthenticate</a></div><div class="ttdef"><b>Definition:</b> BMAAuthenticate.h:7</div></div>
|
|
||||||
</div><!-- fragment --></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.13
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Development/BMA/server_core/ServerCore/BMACommand.h Source File</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.13 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="menudata.js"></script>
|
|
||||||
<script type="text/javascript" src="menu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
initMenu('',true,false,'search.php','Search');
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div id="main-nav"></div>
|
|
||||||
</div><!-- top -->
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Development/BMA/server_core/ServerCore/BMACommand.h</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="preprocessor">#ifndef BMACommand_h__</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="preprocessor">#define BMACommand_h__</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> </div><div class="line"><a name="l00004"></a><span class="lineno"> 4</span> <span class="preprocessor">#include "includes"</span></div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> <span class="preprocessor">#include "BMAObject.h"</span></div><div class="line"><a name="l00006"></a><span class="lineno"> 6</span> <span class="keyword">class </span><a class="code" href="class_b_m_a_session.html">BMASession</a>;</div><div class="line"><a name="l00007"></a><span class="lineno"> 7</span> </div><div class="line"><a name="l00008"></a><span class="lineno"><a class="line" href="class_b_m_a_command.html"> 8</a></span> <span class="keyword">class </span><a class="code" href="class_b_m_a_command.html">BMACommand</a> : <span class="keyword">public</span> <a class="code" href="class_b_m_a_object.html">BMAObject</a> {</div><div class="line"><a name="l00009"></a><span class="lineno"> 9</span>  </div><div class="line"><a name="l00010"></a><span class="lineno"> 10</span>  <span class="keyword">public</span>:</div><div class="line"><a name="l00011"></a><span class="lineno"> 11</span>  <a class="code" href="class_b_m_a_command.html">BMACommand</a>(std::string commandName);</div><div class="line"><a name="l00012"></a><span class="lineno"> 12</span>  ~<a class="code" href="class_b_m_a_command.html">BMACommand</a>(); </div><div class="line"><a name="l00013"></a><span class="lineno"> 13</span>  </div><div class="line"><a name="l00014"></a><span class="lineno"> 14</span>  std::string commandName;</div><div class="line"><a name="l00015"></a><span class="lineno"> 15</span> </div><div class="line"><a name="l00016"></a><span class="lineno"> 16</span>  <span class="keyword">virtual</span> <span class="keywordtype">void</span> processCommand(std::string command, <a class="code" href="class_b_m_a_session.html">BMASession</a> *session) = 0;</div><div class="line"><a name="l00017"></a><span class="lineno"> 17</span>  <span class="keyword">virtual</span> <span class="keywordtype">void</span> output(<a class="code" href="class_b_m_a_session.html">BMASession</a> *session);</div><div class="line"><a name="l00018"></a><span class="lineno"> 18</span> </div><div class="line"><a name="l00019"></a><span class="lineno"> 19</span> };</div><div class="line"><a name="l00020"></a><span class="lineno"> 20</span> </div><div class="line"><a name="l00021"></a><span class="lineno"> 21</span> <span class="preprocessor">#endif</span></div><div class="ttc" id="class_b_m_a_command_html"><div class="ttname"><a href="class_b_m_a_command.html">BMACommand</a></div><div class="ttdef"><b>Definition:</b> BMACommand.h:8</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_session_html"><div class="ttname"><a href="class_b_m_a_session.html">BMASession</a></div><div class="ttdef"><b>Definition:</b> BMASession.h:18</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_object_html"><div class="ttname"><a href="class_b_m_a_object.html">BMAObject</a></div><div class="ttdef"><b>Definition:</b> BMAObject.h:6</div></div>
|
|
||||||
</div><!-- fragment --></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.13
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMACommandSession.h Source File</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.13 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="menudata.js"></script>
|
|
||||||
<script type="text/javascript" src="menu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
initMenu('',true,false,'search.php','Search');
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div id="main-nav"></div>
|
|
||||||
</div><!-- top -->
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMACommandSession.h</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="preprocessor">#ifndef __BMACommandSession_h__</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="preprocessor">#define __BMACommandSession_h__</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> </div><div class="line"><a name="l00004"></a><span class="lineno"> 4</span> <span class="preprocessor">#include "BMASession.h"</span></div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> </div><div class="line"><a name="l00013"></a><span class="lineno"> 13</span> </div><div class="line"><a name="l00014"></a><span class="lineno"><a class="line" href="class_b_m_a_command_session.html"> 14</a></span> <span class="keyword">class </span><a class="code" href="class_b_m_a_command_session.html">BMACommandSession</a> : <span class="keyword">public</span> <a class="code" href="class_b_m_a_session.html">BMASession</a> {</div><div class="line"><a name="l00015"></a><span class="lineno"> 15</span>  </div><div class="line"><a name="l00016"></a><span class="lineno"> 16</span>  <span class="keyword">public</span>:</div><div class="line"><a name="l00017"></a><span class="lineno"> 17</span>  <a class="code" href="class_b_m_a_command_session.html">BMACommandSession</a>(<a class="code" href="class_b_m_a_e_poll.html">BMAEPoll</a> &ePoll);</div><div class="line"><a name="l00018"></a><span class="lineno"> 18</span>  ~<a class="code" href="class_b_m_a_command_session.html">BMACommandSession</a>(); </div><div class="line"><a name="l00019"></a><span class="lineno"> 19</span>  </div><div class="line"><a name="l00020"></a><span class="lineno"> 20</span> <span class="comment">// string command;</span></div><div class="line"><a name="l00021"></a><span class="lineno"> 21</span>  </div><div class="line"><a name="l00022"></a><span class="lineno"> 22</span>  <span class="keyword">virtual</span> <span class="keywordtype">void</span> <a class="code" href="class_b_m_a_command_session.html#a72e11ea685f9bbc32f66d42ab5a1627d">output</a>(stringstream &out);</div><div class="line"><a name="l00023"></a><span class="lineno"> 23</span> </div><div class="line"><a name="l00024"></a><span class="lineno"> 24</span>  <span class="keyword">protected</span>:</div><div class="line"><a name="l00025"></a><span class="lineno"> 25</span> <span class="comment">// virtual void onConnected(); </span></div><div class="line"><a name="l00026"></a><span class="lineno"> 26</span> <span class="comment">// virtual void onDataReceived(char *data, int length); </span></div><div class="line"><a name="l00027"></a><span class="lineno"> 27</span>  <span class="keywordtype">void</span> protocol(<span class="keywordtype">char</span> *data, <span class="keywordtype">int</span> length) <span class="keyword">override</span>; </div><div class="line"><a name="l00028"></a><span class="lineno"> 28</span>  </div><div class="line"><a name="l00029"></a><span class="lineno"> 29</span>  <span class="keyword">private</span>:</div><div class="line"><a name="l00030"></a><span class="lineno"> 30</span>  <span class="keyword">enum</span> Status {WELCOME, PROMPT, INPUT, PROCESS, DONE};</div><div class="line"><a name="l00031"></a><span class="lineno"> 31</span>  Status status = WELCOME; </div><div class="line"><a name="l00032"></a><span class="lineno"> 32</span>  </div><div class="line"><a name="l00033"></a><span class="lineno"> 33</span> };</div><div class="line"><a name="l00034"></a><span class="lineno"> 34</span> </div><div class="line"><a name="l00035"></a><span class="lineno"> 35</span> <span class="preprocessor">#endif</span></div><div class="ttc" id="class_b_m_a_e_poll_html"><div class="ttname"><a href="class_b_m_a_e_poll.html">BMAEPoll</a></div><div class="ttdef"><b>Definition:</b> BMAEPoll.h:29</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_command_session_html_a72e11ea685f9bbc32f66d42ab5a1627d"><div class="ttname"><a href="class_b_m_a_command_session.html#a72e11ea685f9bbc32f66d42ab5a1627d">BMACommandSession::output</a></div><div class="ttdeci">virtual void output(stringstream &out)</div><div class="ttdef"><b>Definition:</b> BMACommandSession.cpp:10</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_command_session_html"><div class="ttname"><a href="class_b_m_a_command_session.html">BMACommandSession</a></div><div class="ttdef"><b>Definition:</b> BMACommandSession.h:14</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_session_html"><div class="ttname"><a href="class_b_m_a_session.html">BMASession</a></div><div class="ttdef"><b>Definition:</b> BMASession.h:16</div></div>
|
|
||||||
</div><!-- fragment --></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.13
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAConsole.cpp File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAConsole.cpp File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "<a class="el" href="_b_m_a_e_poll_8h_source.html">BMAEPoll.h</a>"</code><br />
|
|
||||||
<code>#include "<a class="el" href="_b_m_a_console_8h_source.html">BMAConsole.h</a>"</code><br />
|
|
||||||
<code>#include "<a class="el" href="_b_m_a_console_session_8h_source.html">BMAConsoleSession.h</a>"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAConsole.cpp:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_8cpp__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8cpp" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8cpp" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8cpp">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_e_poll_8h.html" title="BMAEPoll.h" alt="" coords="205,184,299,211"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_console_8h.html" title="BMAConsole.h" alt="" coords="198,408,309,435"/>
|
|
||||||
<area shape="rect" id="node13" href="_b_m_a_console_session_8h.html" title="BMAConsoleSession.h" alt="" coords="111,109,268,136"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="67,632,170,659"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_thread_8h.html" title="BMAThread.h" alt="" coords="290,259,393,285"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="5,707,101,733"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="177,707,287,733"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="295,333,377,360"/>
|
|
||||||
<area shape="rect" id="node10" href="_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="153,483,321,509"/>
|
|
||||||
<area shape="rect" id="node12" href="_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="291,557,461,584"/>
|
|
||||||
<area shape="rect" id="node11" href="_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="136,557,267,584"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAConsole.cpp" name="/home/barant/Documents/Development/BMASockets/BMAConsole.cpp">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_e_poll_8h.html" title="BMAEPoll.h" alt="" coords="205,184,299,211"/>
|
|
||||||
<area shape="rect" id="node9" href="$_b_m_a_console_8h.html" title="BMAConsole.h" alt="" coords="198,408,309,435"/>
|
|
||||||
<area shape="rect" id="node13" href="$_b_m_a_console_session_8h.html" title="BMAConsoleSession.h" alt="" coords="111,109,268,136"/>
|
|
||||||
<area shape="rect" id="node3" href="$_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="67,632,170,659"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_thread_8h.html" title="BMAThread.h" alt="" coords="290,259,393,285"/>
|
|
||||||
<area shape="rect" id="node5" href="$_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="5,707,101,733"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="177,707,287,733"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="295,333,377,360"/>
|
|
||||||
<area shape="rect" id="node10" href="$_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="153,483,321,509"/>
|
|
||||||
<area shape="rect" id="node12" href="$_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="291,557,461,584"/>
|
|
||||||
<area shape="rect" id="node11" href="$_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="136,557,267,584"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
6068b19be17a126ec437ed1ad655c961
|
|
||||||
|
Before Width: | Height: | Size: 81 KiB |
@@ -1,150 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAConsole.h File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="summary">
|
|
||||||
<a href="#nested-classes">Classes</a> </div>
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAConsole.h File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "<a class="el" href="_b_m_a_t_c_p_server_socket_8h_source.html">BMATCPServerSocket.h</a>"</code><br />
|
|
||||||
<code>#include "<a class="el" href="_b_m_a_console_command_8h_source.html">BMAConsoleCommand.h</a>"</code><br />
|
|
||||||
<code>#include "<a class="el" href="_b_m_a_log_8h_source.html">BMALog.h</a>"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAConsole.h:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_8h__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8h" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8h" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8h">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="111,109,279,136"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="258,184,429,211"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="381,109,463,136"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="20,259,123,285"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="103,184,234,211"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="23,333,119,360"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="144,333,255,360"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
This graph shows which files directly or indirectly include this file:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_8h__dep__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8hdep" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8hdep" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_8hdep">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_log_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.h" alt="" coords="1285,109,1469,165"/>
|
|
||||||
<area shape="rect" id="node4" href="_b_m_a_e_poll_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.h" alt="" coords="1221,317,1405,373"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_console_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsole.cpp" alt="" coords="5,525,189,581"/>
|
|
||||||
<area shape="rect" id="node21" href="main_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/main.cpp" alt="" coords="2312,421,2496,477"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_thread_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.h" alt="" coords="1285,213,1469,269"/>
|
|
||||||
<area shape="rect" id="node22" href="_b_m_a_log_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.cpp" alt="" coords="1493,213,1677,269"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_e_poll_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.cpp" alt="" coords="635,525,819,581"/>
|
|
||||||
<area shape="rect" id="node19" href="_b_m_a_thread_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.cpp" alt="" coords="2104,421,2288,477"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_console_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.h" alt="" coords="515,421,699,477"/>
|
|
||||||
<area shape="rect" id="node10" href="_b_m_a_t_c_p_server_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPServerSocket.cpp" alt="" coords="843,525,1029,581"/>
|
|
||||||
<area shape="rect" id="node11" href="_b_m_a_h_t_t_p_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPServer.cpp" alt="" coords="1053,525,1237,581"/>
|
|
||||||
<area shape="rect" id="node12" href="_b_m_a_h_t_t_p_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.h" alt="" coords="1013,421,1197,477"/>
|
|
||||||
<area shape="rect" id="node14" href="_b_m_a_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMASocket.cpp" alt="" coords="1221,421,1405,477"/>
|
|
||||||
<area shape="rect" id="node15" href="_b_m_a_stream_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamServer.cpp" alt="" coords="1469,525,1653,581"/>
|
|
||||||
<area shape="rect" id="node16" href="_b_m_a_stream_socket_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.h" alt="" coords="1480,421,1664,477"/>
|
|
||||||
<area shape="rect" id="node18" href="_b_m_a_t_c_p_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPSocket.cpp" alt="" coords="1688,421,1872,477"/>
|
|
||||||
<area shape="rect" id="node20" href="_b_m_a_timer_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATimer.cpp" alt="" coords="1896,421,2080,477"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_console_command_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleCommand.cpp" alt="" coords="214,525,402,581"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_console_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.cpp" alt="" coords="427,525,611,581"/>
|
|
||||||
<area shape="rect" id="node13" href="_b_m_a_h_t_t_p_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.cpp" alt="" coords="1261,525,1445,581"/>
|
|
||||||
<area shape="rect" id="node17" href="_b_m_a_stream_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.cpp" alt="" coords="1677,525,1861,581"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p><a href="_b_m_a_console_8h_source.html">Go to the source code of this file.</a></p>
|
|
||||||
<table class="memberdecls">
|
|
||||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a>
|
|
||||||
Classes</h2></td></tr>
|
|
||||||
<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_b_m_a_console.html">BMAConsole</a></td></tr>
|
|
||||||
<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr>
|
|
||||||
</table>
|
|
||||||
</div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAConsole.h" name="/home/barant/Documents/Development/BMASockets/BMAConsole.h">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_log_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.h" alt="" coords="1285,109,1469,165"/>
|
|
||||||
<area shape="rect" id="node4" href="$_b_m_a_e_poll_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.h" alt="" coords="1221,317,1405,373"/>
|
|
||||||
<area shape="rect" id="node5" href="$_b_m_a_console_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsole.cpp" alt="" coords="5,525,189,581"/>
|
|
||||||
<area shape="rect" id="node21" href="$main_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/main.cpp" alt="" coords="2312,421,2496,477"/>
|
|
||||||
<area shape="rect" id="node3" href="$_b_m_a_thread_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.h" alt="" coords="1285,213,1469,269"/>
|
|
||||||
<area shape="rect" id="node22" href="$_b_m_a_log_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.cpp" alt="" coords="1493,213,1677,269"/>
|
|
||||||
<area shape="rect" id="node9" href="$_b_m_a_e_poll_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.cpp" alt="" coords="635,525,819,581"/>
|
|
||||||
<area shape="rect" id="node19" href="$_b_m_a_thread_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.cpp" alt="" coords="2104,421,2288,477"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_console_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.h" alt="" coords="515,421,699,477"/>
|
|
||||||
<area shape="rect" id="node10" href="$_b_m_a_t_c_p_server_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPServerSocket.cpp" alt="" coords="843,525,1029,581"/>
|
|
||||||
<area shape="rect" id="node11" href="$_b_m_a_h_t_t_p_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPServer.cpp" alt="" coords="1053,525,1237,581"/>
|
|
||||||
<area shape="rect" id="node12" href="$_b_m_a_h_t_t_p_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.h" alt="" coords="1013,421,1197,477"/>
|
|
||||||
<area shape="rect" id="node14" href="$_b_m_a_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMASocket.cpp" alt="" coords="1221,421,1405,477"/>
|
|
||||||
<area shape="rect" id="node15" href="$_b_m_a_stream_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamServer.cpp" alt="" coords="1469,525,1653,581"/>
|
|
||||||
<area shape="rect" id="node16" href="$_b_m_a_stream_socket_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.h" alt="" coords="1480,421,1664,477"/>
|
|
||||||
<area shape="rect" id="node18" href="$_b_m_a_t_c_p_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPSocket.cpp" alt="" coords="1688,421,1872,477"/>
|
|
||||||
<area shape="rect" id="node20" href="$_b_m_a_timer_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATimer.cpp" alt="" coords="1896,421,2080,477"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_console_command_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleCommand.cpp" alt="" coords="214,525,402,581"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_console_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.cpp" alt="" coords="427,525,611,581"/>
|
|
||||||
<area shape="rect" id="node13" href="$_b_m_a_h_t_t_p_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.cpp" alt="" coords="1261,525,1445,581"/>
|
|
||||||
<area shape="rect" id="node17" href="$_b_m_a_stream_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.cpp" alt="" coords="1677,525,1861,581"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
11c30597ee7e0116a48afaee64e1854c
|
|
||||||
|
Before Width: | Height: | Size: 146 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAConsole.h" name="/home/barant/Documents/Development/BMASockets/BMAConsole.h">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="111,109,279,136"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="258,184,429,211"/>
|
|
||||||
<area shape="rect" id="node9" href="$_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="381,109,463,136"/>
|
|
||||||
<area shape="rect" id="node3" href="$_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="20,259,123,285"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="103,184,234,211"/>
|
|
||||||
<area shape="rect" id="node5" href="$_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="23,333,119,360"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="144,333,255,360"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
a75b2e10971093e0c0adfe3aadf8dcbd
|
|
||||||
|
Before Width: | Height: | Size: 39 KiB |
@@ -1,78 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAConsole.h Source File</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.13 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="menudata.js"></script>
|
|
||||||
<script type="text/javascript" src="menu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
initMenu('',true,false,'search.php','Search');
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div id="main-nav"></div>
|
|
||||||
</div><!-- top -->
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAConsole.h</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="preprocessor">#ifndef BMAConsole_h__</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="preprocessor">#define BMAConsole_h__</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> </div><div class="line"><a name="l00004"></a><span class="lineno"> 4</span> <span class="preprocessor">#include "includes"</span></div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> <span class="preprocessor">#include "BMATCPServerSocket.h"</span></div><div class="line"><a name="l00006"></a><span class="lineno"> 6</span> <span class="comment">//#include "BMACommand.h"</span></div><div class="line"><a name="l00007"></a><span class="lineno"> 7</span> <span class="keyword">class </span><a class="code" href="class_b_m_a_t_c_p_socket.html">BMATCPSocket</a>;</div><div class="line"><a name="l00008"></a><span class="lineno"> 8</span> </div><div class="line"><a name="l00009"></a><span class="lineno"><a class="line" href="class_b_m_a_console.html"> 9</a></span> <span class="keyword">class </span><a class="code" href="class_b_m_a_console.html">BMAConsole</a> : <span class="keyword">public</span> <a class="code" href="class_b_m_a_t_c_p_server_socket.html">BMATCPServerSocket</a> {</div><div class="line"><a name="l00010"></a><span class="lineno"> 10</span>  </div><div class="line"><a name="l00011"></a><span class="lineno"> 11</span>  <span class="keyword">public</span>:</div><div class="line"><a name="l00012"></a><span class="lineno"> 12</span>  <a class="code" href="class_b_m_a_console.html">BMAConsole</a>(<a class="code" href="class_b_m_a_e_poll.html">BMAEPoll</a> &ePoll, <span class="keywordtype">string</span> url, <span class="keywordtype">short</span> <span class="keywordtype">int</span> port);</div><div class="line"><a name="l00013"></a><span class="lineno"> 13</span>  ~<a class="code" href="class_b_m_a_console.html">BMAConsole</a>(); </div><div class="line"><a name="l00014"></a><span class="lineno"> 14</span> </div><div class="line"><a name="l00015"></a><span class="lineno"> 15</span>  <a class="code" href="class_b_m_a_session.html">BMASession</a> * getSocketAccept();</div><div class="line"><a name="l00016"></a><span class="lineno"> 16</span> </div><div class="line"><a name="l00017"></a><span class="lineno"> 17</span> <span class="comment">// void registerCommand(BMACommand &command);</span></div><div class="line"><a name="l00018"></a><span class="lineno"> 18</span> </div><div class="line"><a name="l00019"></a><span class="lineno"> 19</span> <span class="comment">// vector<BMACommand *> commands; </span></div><div class="line"><a name="l00020"></a><span class="lineno"> 20</span>  </div><div class="line"><a name="l00021"></a><span class="lineno"> 21</span> <span class="comment">// void protocolHandler() {</span></div><div class="line"><a name="l00022"></a><span class="lineno"> 22</span>  </div><div class="line"><a name="l00023"></a><span class="lineno"> 23</span> <span class="comment">// BMAAnnouncer announcer("Welcome to BMA Server Framework console.\n"); </span></div><div class="line"><a name="l00024"></a><span class="lineno"> 24</span> <span class="comment">// addProtocol(announcer);</span></div><div class="line"><a name="l00025"></a><span class="lineno"> 25</span> <span class="comment">// BMACommandProcessor commandProcess();</span></div><div class="line"><a name="l00026"></a><span class="lineno"> 26</span>  </div><div class="line"><a name="l00027"></a><span class="lineno"> 27</span> <span class="comment">// }</span></div><div class="line"><a name="l00028"></a><span class="lineno"> 28</span> </div><div class="line"><a name="l00029"></a><span class="lineno"> 29</span> };</div><div class="line"><a name="l00030"></a><span class="lineno"> 30</span> </div><div class="line"><a name="l00031"></a><span class="lineno"> 31</span> <span class="preprocessor">#endif</span></div><div class="ttc" id="class_b_m_a_t_c_p_socket_html"><div class="ttname"><a href="class_b_m_a_t_c_p_socket.html">BMATCPSocket</a></div><div class="ttdef"><b>Definition:</b> BMATCPSocket.h:17</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_console_html"><div class="ttname"><a href="class_b_m_a_console.html">BMAConsole</a></div><div class="ttdef"><b>Definition:</b> BMAConsole.h:9</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_t_c_p_server_socket_html"><div class="ttname"><a href="class_b_m_a_t_c_p_server_socket.html">BMATCPServerSocket</a></div><div class="ttdef"><b>Definition:</b> BMATCPServerSocket.h:15</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_e_poll_html"><div class="ttname"><a href="class_b_m_a_e_poll.html">BMAEPoll</a></div><div class="ttdef"><b>Definition:</b> BMAEPoll.h:29</div></div>
|
|
||||||
<div class="ttc" id="class_b_m_a_session_html"><div class="ttname"><a href="class_b_m_a_session.html">BMASession</a></div><div class="ttdef"><b>Definition:</b> BMASession.h:16</div></div>
|
|
||||||
</div><!-- fragment --></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.13
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAConsoleCommand.cpp File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAConsoleCommand.cpp File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "<a class="el" href="_b_m_a_console_command_8h_source.html">BMAConsoleCommand.h</a>"</code><br />
|
|
||||||
<code>#include "<a class="el" href="_b_m_a_console_session_8h_source.html">BMAConsoleSession.h</a>"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAConsoleCommand.cpp:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_command_8cpp__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8cpp" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8cpp" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8cpp">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="144,557,315,584"/>
|
|
||||||
<area shape="rect" id="node4" href="_b_m_a_console_session_8h.html" title="BMAConsoleSession.h" alt="" coords="173,109,331,136"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="440,557,571,584"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_e_poll_8h.html" title="BMAEPoll.h" alt="" coords="205,184,299,211"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="326,632,429,659"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="245,707,341,733"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="366,707,477,733"/>
|
|
||||||
<area shape="rect" id="node10" href="_b_m_a_thread_8h.html" title="BMAThread.h" alt="" coords="111,259,214,285"/>
|
|
||||||
<area shape="rect" id="node12" href="_b_m_a_console_8h.html" title="BMAConsole.h" alt="" coords="145,408,255,435"/>
|
|
||||||
<area shape="rect" id="node11" href="_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="127,333,209,360"/>
|
|
||||||
<area shape="rect" id="node13" href="_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="197,483,365,509"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div></div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<map id="/home/barant/Documents/Development/BMASockets/BMAConsoleCommand.cpp" name="/home/barant/Documents/Development/BMASockets/BMAConsoleCommand.cpp">
|
|
||||||
<area shape="rect" id="node2" href="$_b_m_a_console_command_8h.html" title="BMAConsoleCommand.h" alt="" coords="144,557,315,584"/>
|
|
||||||
<area shape="rect" id="node4" href="$_b_m_a_console_session_8h.html" title="BMAConsoleSession.h" alt="" coords="173,109,331,136"/>
|
|
||||||
<area shape="rect" id="node5" href="$_b_m_a_t_c_p_socket_8h.html" title="BMATCPSocket.h" alt="" coords="440,557,571,584"/>
|
|
||||||
<area shape="rect" id="node9" href="$_b_m_a_e_poll_8h.html" title="BMAEPoll.h" alt="" coords="205,184,299,211"/>
|
|
||||||
<area shape="rect" id="node6" href="$_b_m_a_socket_8h.html" title="BMASocket.h" alt="" coords="326,632,429,659"/>
|
|
||||||
<area shape="rect" id="node7" href="$_b_m_a_event_8h.html" title="BMAEvent.h" alt="" coords="245,707,341,733"/>
|
|
||||||
<area shape="rect" id="node8" href="$_b_m_a_property_8h.html" title="BMAProperty.h" alt="" coords="366,707,477,733"/>
|
|
||||||
<area shape="rect" id="node10" href="$_b_m_a_thread_8h.html" title="BMAThread.h" alt="" coords="111,259,214,285"/>
|
|
||||||
<area shape="rect" id="node12" href="$_b_m_a_console_8h.html" title="BMAConsole.h" alt="" coords="145,408,255,435"/>
|
|
||||||
<area shape="rect" id="node11" href="$_b_m_a_log_8h.html" title="BMALog.h" alt="" coords="127,333,209,360"/>
|
|
||||||
<area shape="rect" id="node13" href="$_b_m_a_t_c_p_server_socket_8h.html" title="BMATCPServerSocket.h" alt="" coords="197,483,365,509"/>
|
|
||||||
</map>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
12c6b7c7bbfaeec186638b4c66cc0276
|
|
||||||
|
Before Width: | Height: | Size: 69 KiB |
@@ -1,151 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
||||||
<meta name="generator" content="Doxygen 1.8.11"/>
|
|
||||||
<title>BMA Server Framework: /home/barant/Documents/Development/BMASockets/BMAConsoleCommand.h File Reference</title>
|
|
||||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
|
||||||
<script type="text/javascript" src="dynsections.js"></script>
|
|
||||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
||||||
<script type="text/javascript" src="search/search.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() { init_search(); });
|
|
||||||
</script>
|
|
||||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
||||||
<div id="titlearea">
|
|
||||||
<table cellspacing="0" cellpadding="0">
|
|
||||||
<tbody>
|
|
||||||
<tr style="height: 56px;">
|
|
||||||
<td id="projectalign" style="padding-left: 0.5em;">
|
|
||||||
<div id="projectname">BMA Server Framework
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- end header part -->
|
|
||||||
<!-- Generated by Doxygen 1.8.11 -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
||||||
</script>
|
|
||||||
<div id="navrow1" class="tabs">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="index.html"><span>Main Page</span></a></li>
|
|
||||||
<li><a href="annotated.html"><span>Classes</span></a></li>
|
|
||||||
<li class="current"><a href="files.html"><span>Files</span></a></li>
|
|
||||||
<li>
|
|
||||||
<div id="MSearchBox" class="MSearchBoxInactive">
|
|
||||||
<span class="left">
|
|
||||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
alt=""/>
|
|
||||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
|
||||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
|
||||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
|
||||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
|
||||||
</span><span class="right">
|
|
||||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="navrow2" class="tabs2">
|
|
||||||
<ul class="tablist">
|
|
||||||
<li><a href="files.html"><span>File List</span></a></li>
|
|
||||||
<li><a href="globals.html"><span>File Members</span></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- window showing the filter options -->
|
|
||||||
<div id="MSearchSelectWindow"
|
|
||||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
||||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
||||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- iframe showing the search results (closed by default) -->
|
|
||||||
<div id="MSearchResultsWindow">
|
|
||||||
<iframe src="javascript:void(0)" frameborder="0"
|
|
||||||
name="MSearchResults" id="MSearchResults">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- top -->
|
|
||||||
<div class="header">
|
|
||||||
<div class="summary">
|
|
||||||
<a href="#nested-classes">Classes</a> </div>
|
|
||||||
<div class="headertitle">
|
|
||||||
<div class="title">/home/barant/Documents/Development/BMASockets/BMAConsoleCommand.h File Reference</div> </div>
|
|
||||||
</div><!--header-->
|
|
||||||
<div class="contents">
|
|
||||||
<div class="textblock"><code>#include "includes"</code><br />
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
Include dependency graph for BMAConsoleCommand.h:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_command_8h__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8h" alt=""/></div>
|
|
||||||
</div>
|
|
||||||
</div><div class="textblock"><div class="dynheader">
|
|
||||||
This graph shows which files directly or indirectly include this file:</div>
|
|
||||||
<div class="dyncontent">
|
|
||||||
<div class="center"><img src="_b_m_a_console_command_8h__dep__incl.png" border="0" usemap="#_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8hdep" alt=""/></div>
|
|
||||||
<map name="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8hdep" id="_2home_2barant_2_documents_2_development_2_b_m_a_sockets_2_b_m_a_console_command_8hdep">
|
|
||||||
<area shape="rect" id="node2" href="_b_m_a_t_c_p_server_socket_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPServerSocket.h" alt="" coords="2018,109,2202,165"/>
|
|
||||||
<area shape="rect" id="node5" href="_b_m_a_console_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsole.h" alt="" coords="962,213,1146,269"/>
|
|
||||||
<area shape="rect" id="node11" href="_b_m_a_console_command_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleCommand.cpp" alt="" coords="5,733,193,789"/>
|
|
||||||
<area shape="rect" id="node3" href="_b_m_a_authenticate_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAAuthenticate.h" alt="" coords="2147,213,2331,269"/>
|
|
||||||
<area shape="rect" id="node14" href="_b_m_a_t_c_p_server_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPServerSocket.cpp" alt="" coords="1091,733,1278,789"/>
|
|
||||||
<area shape="rect" id="node25" href="main_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/main.cpp" alt="" coords="2385,629,2569,685"/>
|
|
||||||
<area shape="rect" id="node27" href="_b_m_a_h_t_t_p_server_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPServer.h" alt="" coords="2046,421,2230,477"/>
|
|
||||||
<area shape="rect" id="node30" href="_b_m_a_stream_server_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamServer.h" alt="" coords="2502,317,2686,373"/>
|
|
||||||
<area shape="rect" id="node4" href="_b_m_a_authenticate_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAAuthenticate.cpp" alt="" coords="2147,317,2331,373"/>
|
|
||||||
<area shape="rect" id="node6" href="_b_m_a_log_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.h" alt="" coords="898,317,1082,373"/>
|
|
||||||
<area shape="rect" id="node8" href="_b_m_a_e_poll_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.h" alt="" coords="1027,525,1211,581"/>
|
|
||||||
<area shape="rect" id="node9" href="_b_m_a_console_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsole.cpp" alt="" coords="218,733,402,789"/>
|
|
||||||
<area shape="rect" id="node7" href="_b_m_a_thread_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.h" alt="" coords="898,421,1082,477"/>
|
|
||||||
<area shape="rect" id="node26" href="_b_m_a_log_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMALog.cpp" alt="" coords="690,421,874,477"/>
|
|
||||||
<area shape="rect" id="node13" href="_b_m_a_e_poll_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAEPoll.cpp" alt="" coords="634,733,818,789"/>
|
|
||||||
<area shape="rect" id="node23" href="_b_m_a_thread_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAThread.cpp" alt="" coords="187,629,371,685"/>
|
|
||||||
<area shape="rect" id="node10" href="_b_m_a_console_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.h" alt="" coords="446,629,630,685"/>
|
|
||||||
<area shape="rect" id="node15" href="_b_m_a_h_t_t_p_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPServer.cpp" alt="" coords="1998,733,2182,789"/>
|
|
||||||
<area shape="rect" id="node16" href="_b_m_a_h_t_t_p_session_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.h" alt="" coords="1790,629,1974,685"/>
|
|
||||||
<area shape="rect" id="node18" href="_b_m_a_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMASocket.cpp" alt="" coords="1222,629,1406,685"/>
|
|
||||||
<area shape="rect" id="node19" href="_b_m_a_stream_server_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamServer.cpp" alt="" coords="1582,733,1766,789"/>
|
|
||||||
<area shape="rect" id="node20" href="_b_m_a_stream_socket_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.h" alt="" coords="1481,629,1665,685"/>
|
|
||||||
<area shape="rect" id="node22" href="_b_m_a_t_c_p_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATCPSocket.cpp" alt="" coords="755,629,939,685"/>
|
|
||||||
<area shape="rect" id="node24" href="_b_m_a_timer_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMATimer.cpp" alt="" coords="963,629,1147,685"/>
|
|
||||||
<area shape="rect" id="node12" href="_b_m_a_console_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAConsoleSession.cpp" alt="" coords="426,733,610,789"/>
|
|
||||||
<area shape="rect" id="node17" href="_b_m_a_h_t_t_p_session_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPSession.cpp" alt="" coords="1790,733,1974,789"/>
|
|
||||||
<area shape="rect" id="node21" href="_b_m_a_stream_socket_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamSocket.cpp" alt="" coords="1374,733,1558,789"/>
|
|
||||||
<area shape="rect" id="node28" href="_b_m_a_h_t_t_p_request_handler_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPRequestHandler.h" alt="" coords="2095,525,2290,581"/>
|
|
||||||
<area shape="rect" id="node29" href="_b_m_a_h_t_t_p_request_handler_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAHTTPRequestHandler.cpp" alt="" coords="2100,629,2309,685"/>
|
|
||||||
<area shape="rect" id="node31" href="_b_m_a_m_p3_stream_content_provider_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAMP3StreamContentProvider.h" alt="" coords="2517,421,2746,477"/>
|
|
||||||
<area shape="rect" id="node32" href="_b_m_a_m_p3_file_8h.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAMP3File.h" alt="" coords="2587,525,2771,581"/>
|
|
||||||
<area shape="rect" id="node34" href="_b_m_a_stream_content_provider_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAStreamContentProvider.cpp" alt="" coords="2770,421,2986,477"/>
|
|
||||||
<area shape="rect" id="node33" href="_b_m_a_m_p3_file_8cpp.html" title="/home/barant/Documents\l/Development/BMASockets\l/BMAMP3File.cpp" alt="" coords="2593,629,2777,685"/>
|
|
||||||
</map>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p><a href="_b_m_a_console_command_8h_source.html">Go to the source code of this file.</a></p>
|
|
||||||
<table class="memberdecls">
|
|
||||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a>
|
|
||||||
Classes</h2></td></tr>
|
|
||||||
<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_b_m_a_console_command.html">BMAConsoleCommand</a></td></tr>
|
|
||||||
<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr>
|
|
||||||
</table>
|
|
||||||
</div><!-- contents -->
|
|
||||||
<!-- start footer part -->
|
|
||||||
<hr class="footer"/><address class="footer"><small>
|
|
||||||
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
||||||
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
||||||
</a> 1.8.11
|
|
||||||
</small></address>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||