mirror of
https://github.com/dmcxblue/ANIMO
synced 2026-08-04 16:10:26 +00:00
Fixes intermittent "command completes with no terminal output" and "session table shows Unknown/N/A" bugs, plus adds a couple of diagnostic knobs so future issues are easier to root-cause. - sendTo() returns bool and now calls sock->flush() after write, so the Qt write buffer never sits on stdout under event-loop starvation. All broadcast call sites migrated to broadcastToSession(), which prunes dead sockets on write failure. - SessionDBManager: updateSessionUser / updateSessionTenant / setSessionAlive / setSessionStatus wrap their SQL in QMutexLocker(&m_mutex) to fix intermittent SQLITE_BUSY under concurrent writes. - addSessionToMainDB uses INSERT ... ON CONFLICT DO UPDATE SET so re-adding a session preserves CreatedBy instead of clobbering it. - setSessionAlive() / setSessionStatus() wired into the session lifecycle (ensureProcess, session_exited, LOGIN_OK/LOGIN_FAIL) so the Alive/Status columns reflect real state. - Nested-mutex audit: removed a nested QMutexLocker(&g_stateMutex) inside readyReadStandardOutput's pending-reinject block. Qt's QMutex is non-recursive by default; the inner lock was the "run one command and that's it" deadlock. - Pre-login output allowlist filter (microsoft.com/devicelogin, [Animo], WARNING, ...) so the terminal no longer shows pwsh's stdin echo fragments during login. - update_session_meta RPC handler so a client can heal a stale DB row it detected on its side. - server/main.cpp installs a QMutex-guarded qInstallMessageHandler that mirrors qInfo/qWarning to /tmp/animo-srv-dbg.log across restarts (see DEBUGGING in the repo docs).
75 lines
2.2 KiB
C++
Executable File
75 lines
2.2 KiB
C++
Executable File
#pragma once
|
|
#include <QObject>
|
|
#include <QTcpServer>
|
|
#include <QTcpSocket>
|
|
#include <QSet>
|
|
#include <QHash>
|
|
#include <QJsonObject>
|
|
#include <QString>
|
|
#include <QDir>
|
|
#include <QRegularExpression>
|
|
|
|
// Server owns all session lifecycle, PowerShell processes, and DB state.
|
|
// Clients send JSON actions; server replies + pushes async events.
|
|
class Server : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
explicit Server(const QString &bindIp,
|
|
quint16 port,
|
|
const QString &user,
|
|
const QString &pass,
|
|
QObject *parent = nullptr);
|
|
|
|
// Start listening; also used to perform one-time DB init in .cpp
|
|
bool start();
|
|
|
|
// Update auth credential (simple shared-secret login)
|
|
void setLoginCredential(const QString &user, const QString &pass);
|
|
|
|
signals:
|
|
// Append human-friendly log lines to a UI/console
|
|
void log(const QString &line);
|
|
|
|
private slots:
|
|
// TCP plumbing
|
|
void onNewConnection();
|
|
void onClientReady();
|
|
void onClientDisconnected();
|
|
|
|
private:
|
|
// Route a single JSON line; returns true if handled/formatted
|
|
bool handleLine(QTcpSocket *sock, const QByteArray &line);
|
|
|
|
// Simple password auth gate for this connection
|
|
bool handleLogin(QTcpSocket *sock, const QJsonObject &obj);
|
|
|
|
// Constant-time string comparison to prevent timing attacks
|
|
static bool constantTimeCompare(const QString &a, const QString &b);
|
|
|
|
// Listener + connected sockets
|
|
QTcpServer tcp_;
|
|
QSet<QTcpSocket*> clients_;
|
|
QSet<QTcpSocket*> authed_; // sockets that passed login
|
|
QHash<QTcpSocket*, QString> operatorBySocket_; // authed socket -> operator handle (for attribution)
|
|
|
|
// Bind + auth config
|
|
QString bindIp_;
|
|
quint16 port_;
|
|
QString allowedUser_;
|
|
QString allowedPass_;
|
|
|
|
// Login rate limiting per IP
|
|
struct LoginAttempt {
|
|
int failCount = 0;
|
|
qint64 lastAttemptMs = 0;
|
|
qint64 lockoutUntilMs = 0;
|
|
};
|
|
QHash<QString, LoginAttempt> loginAttempts_;
|
|
|
|
// Server limits
|
|
static constexpr int MAX_SESSIONS = 100;
|
|
static constexpr int MAX_CMD_LENGTH = 100000; // 100KB
|
|
static constexpr int MAX_LOGIN_FAILURES = 5;
|
|
static constexpr qint64 LOGIN_LOCKOUT_MS = 60000; // 1 minute lockout
|
|
};
|