Add real authentication to ConsoleServer/ConsoleSession, fix CommandList help

ConsoleSession's login flow never actually checked anything: the
entered username was discarded and the password was never compared,
so any input (including blank) reached the command prompt. Confirmed
live via an unauthenticated login against BARANTMail's admin console
on port 1027, which was reachable externally. ConsoleServer now takes
an optional credentialChecker callback consulted by the WAIT_PASSWORD
state; omitting it preserves the old accept-anything behavior for any
other use of this class.

Separately, the console never prompted until the client's first line
arrived - that line was silently discarded just to kick the state
machine off, shifting every subsequent line one slot early. Harmless
under the old accept-anything login, but it broke real credential
checking outright (the typed password was compared against the typed
username, etc). ConsoleSession::onConnected() now starts the state
machine immediately on connect instead.

CommandList::processCommand() was a no-op stub - the intended
"typing help lists the registered commands" body was commented out,
and even referenced a Command::getName() that was never added to the
base class. The map's own key from add() already is the registered
name, so this just walks it directly.
This commit is contained in:
Brad Arant committed 2026-09-06 04:05:59 +00:00
1 parent 2676aaf84c
commit 1a15f6ced8
5 files changed
+78 -5

No files matched your search

+9 -2
View File
@@ -56,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;
} }
+7 -1
View File
@@ -5,7 +5,9 @@
namespace core { namespace core {
ConsoleServer::ConsoleServer(EPoll &ePoll, IPAddress address, TLSInfo *tlsInfo) : TCPServer(ePoll, address, tlsInfo, " ", 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);
} }
@@ -18,4 +20,8 @@ namespace core {
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;
}
} }
+15 -2
View File
@@ -6,6 +6,8 @@
# include "EPoll.h" # include "EPoll.h"
# include "LogListener.h" # include "LogListener.h"
# include "TLSInfo.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, TLSInfo *tlsInfo); 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;
}; };
} }
+23
View File
@@ -1,4 +1,5 @@
#include "ConsoleSession.h" #include "ConsoleSession.h"
#include "ConsoleServer.h"
#include "TCPServer.h" #include "TCPServer.h"
#include "Log.h" #include "Log.h"
@@ -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);
+24
View File
@@ -16,6 +16,14 @@ 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 {
@@ -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;
}; };