diff --git a/AdaptixClient/CMakeLists.txt b/AdaptixClient/CMakeLists.txt index d48527a1..ecd4a200 100644 --- a/AdaptixClient/CMakeLists.txt +++ b/AdaptixClient/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(QT_NO_PRIVATE_MODULE_WARNING ON) + add_subdirectory(Libs/kddockwidgets) if(UNIX) diff --git a/AdaptixClient/Headers/Agent/Commander.h b/AdaptixClient/Headers/Agent/Commander.h index 138b23ca..12cdd32a 100644 --- a/AdaptixClient/Headers/Agent/Commander.h +++ b/AdaptixClient/Headers/Agent/Commander.h @@ -41,6 +41,14 @@ struct CommandsGroup QJSEngine* engine; }; +struct ServerCommandsGroup +{ + QString scriptName; + QString description; + bool enabled = true; + CommandsGroup group; +}; + struct AxExecutor { bool isSet; @@ -69,11 +77,13 @@ Q_OBJECT QString listenerType; QString error; - CommandsGroup regCommandsGroup; - QVector axCommandsGroup; + CommandsGroup mainCommandsGroup; + QMap serverGroups; + QVector clientGroups; QString ProcessPreHook(QJSEngine *engine, const Command &command, const QString &agentId, const QString &cmdline, const QJsonObject &jsonObj, QStringList args); CommanderResult ProcessCommand(const Command &command, const QString &commandName, QStringList args, QJsonObject jsonObj); + CommanderResult ProcessInputForGroup(const CommandsGroup &group, const QString &commandName, QStringList args, const QString &agentId, const QString &cmdline); CommanderResult ProcessHelp(QStringList commandParts); QString GenerateCommandHelp(const Command &command, const QString &parentCommand = ""); @@ -81,9 +91,19 @@ public: explicit Commander(); ~Commander() override; - void AddRegCommands(const CommandsGroup &group); - void AddAxCommands(const CommandsGroup &group); - void RemoveAxCommands(const QString &filepath); + void SetAgentType(const QString &type); + void SetMainCommands(const CommandsGroup &group); + + void AddServerGroup(const QString &scriptName, const QString &description, const CommandsGroup &group); + void RemoveServerGroup(const QString &scriptName); + void SetServerGroupEnabled(const QString &scriptName, bool enabled); + void SetServerGroupEngine(const QString &scriptName, QJSEngine* engine); + bool IsServerGroupEnabled(const QString &scriptName) const; + QStringList GetServerGroupNames() const; + ServerCommandsGroup GetServerGroup(const QString &scriptName) const; + + void AddClientGroup(const CommandsGroup &group); + void RemoveClientGroup(const QString &filepath); QString GetError(); QStringList GetCommands(); diff --git a/AdaptixClient/Headers/Client/AxScript/AxScriptEngine.h b/AdaptixClient/Headers/Client/AxScript/AxScriptEngine.h index bff47b59..4fe9e4aa 100644 --- a/AdaptixClient/Headers/Client/AxScript/AxScriptEngine.h +++ b/AdaptixClient/Headers/Client/AxScript/AxScriptEngine.h @@ -52,6 +52,9 @@ Q_OBJECT std::unique_ptr bridgeEvent; std::unique_ptr bridgeMenu; + bool serverMode = false; + bool scriptEnabled = true; + public: ScriptContext context; @@ -66,6 +69,12 @@ public: AxScriptManager* manager() const; + void setServerMode(bool enabled); + bool isServerMode() const; + + void setEnabled(bool enabled); + bool isEnabled() const; + void registerObject(QObject* obj); void registerAction(QAction* action); void registerEvent(const QString &type, const QJSValue &handler, QTimer* timer, const QSet &list_agents, const QSet &list_os, const QSet &list_listeners, const QString &id); diff --git a/AdaptixClient/Headers/Client/AxScript/AxScriptManager.h b/AdaptixClient/Headers/Client/AxScript/AxScriptManager.h index 12c2ff2c..2ec1e95d 100644 --- a/AdaptixClient/Headers/Client/AxScript/AxScriptManager.h +++ b/AdaptixClient/Headers/Client/AxScript/AxScriptManager.h @@ -40,15 +40,37 @@ struct DataMenuDownload { QString state; }; +struct ServerScriptGroup { + QString agentName; + QString listenerType; + int os; + QString commandsJson; +}; + +struct ServerScriptData { + QString name; + QString description; + QString code; + bool enabled; + QList groups; +}; + +enum class ConfigScriptType { Listener, Agent, Service }; + +struct ConfigScriptEntry { + ConfigScriptType type; + AxScriptEngine* engine; +}; + class AxScriptManager : public QObject { Q_OBJECT AdaptixWidget* adaptixWidget = nullptr; AxScriptEngine* mainScript = nullptr; AxUiFactory* uiFactory = nullptr; - QMap scripts; - QMap listeners_scripts; - QMap agents_scripts; - QMap services_scripts; + QMap scripts; + QMap config_scripts; + QMap server_scripts; + QMap server_scripts_data; public: AxScriptManager(AdaptixWidget* main_widget, QObject *parent = nullptr); @@ -88,6 +110,14 @@ public: bool ScriptAdd(ExtensionFile* ext); void ScriptRemove(const ExtensionFile &ext); + void ServerScriptAdd(const ServerScriptData &data); + void ServerScriptRemove(const QString &name); + void ServerScriptSetEnabled(const QString &name, bool enabled); + bool ServerScriptIsEnabled(const QString &name) const; + QJSEngine* ServerScriptEngine(const QString &name); + QList ServerScriptList() const; + ServerScriptData ServerScriptGet(const QString &name) const; + void GlobalScriptLoad(const QString &path); void GlobalScriptUnload(const QString &path); void GlobalScriptLoadAsync(const QString &path); diff --git a/AdaptixClient/Headers/Client/Requestor.h b/AdaptixClient/Headers/Client/Requestor.h index 97657dcb..8171782b 100644 --- a/AdaptixClient/Headers/Client/Requestor.h +++ b/AdaptixClient/Headers/Client/Requestor.h @@ -67,4 +67,9 @@ void HttpReqChatSendMessageAsync(const QString &text, AuthProfile& profile, cons void HttpReqServiceCallAsync(const QString &service, const QString &command, const QString &args, AuthProfile& profile, const HttpCallback &callback); +void HttpReqAxScriptListAsync(AuthProfile& profile, const HttpCallback &callback); +void HttpReqAxScriptCommandsAsync(AuthProfile& profile, const HttpCallback &callback); +void HttpReqAxScriptLoadAsync(const QString &name, const QString &script, AuthProfile& profile, const HttpCallback &callback); +void HttpReqAxScriptUnloadAsync(const QString &name, AuthProfile& profile, const HttpCallback &callback); + #endif diff --git a/AdaptixClient/Headers/UI/Dialogs/DialogExtender.h b/AdaptixClient/Headers/UI/Dialogs/DialogExtender.h index 4547653c..247592b9 100644 --- a/AdaptixClient/Headers/UI/Dialogs/DialogExtender.h +++ b/AdaptixClient/Headers/UI/Dialogs/DialogExtender.h @@ -4,6 +4,8 @@ #include class Extender; +class AdaptixWidget; +class MainUI; class DialogExtender : public QWidget { @@ -11,13 +13,25 @@ Q_OBJECT Extender* extender = nullptr; QGridLayout* layout = nullptr; - QTableWidget* tableWidget = nullptr; - QSplitter* splitter = nullptr; - QTextEdit* textComment = nullptr; + QTabWidget* tabWidget = nullptr; + + QTableWidget* tableWidget = nullptr; + QSplitter* splitter = nullptr; + QTextEdit* textComment = nullptr; + + QWidget* serverTab = nullptr; + QComboBox* serverProjectCombo = nullptr; + QTableWidget* serverTableWidget = nullptr; + QSplitter* serverSplitter = nullptr; + QTextEdit* serverTextComment = nullptr; + MainUI* mainUI = nullptr; + QPushButton* buttonClose = nullptr; QSpacerItem* spacer1 = nullptr; QSpacerItem* spacer2 = nullptr; + AdaptixWidget* currentAdaptixWidget = nullptr; + void createUI(); public: @@ -28,6 +42,10 @@ public: void UpdateExtenderItem(const ExtensionFile &extenderItem) const; void RemoveExtenderItem(const ExtensionFile &extenderItem) const; + void SetMainUI(MainUI* ui); + void RefreshProjectsList(); + void RefreshServerScripts(); + public Q_SLOTS: void handleMenu(const QPoint &pos ) const; void onActionLoad() const; @@ -36,6 +54,12 @@ public Q_SLOTS: void onActionDisable() const; void onActionRemove() const; void onRowSelect(int row, int column) const; + + void handleServerMenu(const QPoint &pos); + void onServerActionEnable(); + void onServerActionDisable(); + void onServerRowSelect(int row, int column) const; + void onProjectChanged(int index); }; #endif diff --git a/AdaptixClient/Headers/UI/MainUI.h b/AdaptixClient/Headers/UI/MainUI.h index 40ca0a5e..8f4febbb 100644 --- a/AdaptixClient/Headers/UI/MainUI.h +++ b/AdaptixClient/Headers/UI/MainUI.h @@ -34,7 +34,7 @@ public: void onProjectSubscriptions(); void onAxScriptConsole(); - static void onScriptManager(); + void onScriptManager(); static void onSettings(); void AddNewProject(AuthProfile* profile, QThread* channelThread, WebSocketWorker* channelWsWorker); @@ -48,6 +48,7 @@ public: void UpdateTasksTableColumns(); AuthProfile* GetCurrentProfile() const; + QVector GetAdaptixProjects() const; QMenu* getMenuProject() const; QMenu* getMenuAxScript() const; diff --git a/AdaptixClient/Headers/UI/Widgets/AdaptixWidget.h b/AdaptixClient/Headers/UI/Widgets/AdaptixWidget.h index a80fff33..3c38a883 100644 --- a/AdaptixClient/Headers/UI/Widgets/AdaptixWidget.h +++ b/AdaptixClient/Headers/UI/Widgets/AdaptixWidget.h @@ -35,6 +35,7 @@ class TunnelEndpoint; class DialogSyncPacket; class AuthProfile; class AxScriptManager; +struct ServerScriptGroup; typedef struct RegListenerConfig { QString name; @@ -55,6 +56,12 @@ typedef struct AgentTypeInfo { QStringList listenerTypes; } AgentTypeInfo; +struct ServerScriptInfo { + QString name; + QString description; + bool enabled; +}; + class AdaptixWidget : public QWidget { Q_OBJECT @@ -134,29 +141,28 @@ public: TasksWidget* TasksDock = nullptr; TargetsWidget* TargetsDock = nullptr; - QVector RegisterListeners; - QVector RegisterAgents; - QVector ServerRegAgents; - QMap AgentTypes; - QVector Listeners; - QVector Tunnels; - QMap Downloads; - QMap Screenshots; - QVector Credentials; - QVector Targets; - QMap Pivots; - QMap TasksMap; - QMap AgentsMap; - mutable QReadWriteLock AgentsMapLock; - mutable QReadWriteLock TasksMapLock; - mutable QReadWriteLock CredentialsLock; - mutable QReadWriteLock DownloadsLock; - mutable QReadWriteLock ScreenshotsLock; - mutable QReadWriteLock TargetsLock; - mutable QReadWriteLock TunnelsLock; - QMap PostHooksJS; - QMap PostHandlersJS; - QMap ClientTunnels; + QVector RegisterListeners; + QVector RegisterAgents; + QMap AgentTypes; + QVector Listeners; + QVector Tunnels; + QMap Downloads; + QMap Screenshots; + QVector Credentials; + QVector Targets; + QMap Pivots; + QMap TasksMap; + QMap AgentsMap; + mutable QReadWriteLock AgentsMapLock; + mutable QReadWriteLock TasksMapLock; + mutable QReadWriteLock CredentialsLock; + mutable QReadWriteLock DownloadsLock; + mutable QReadWriteLock ScreenshotsLock; + mutable QReadWriteLock TargetsLock; + mutable QReadWriteLock TunnelsLock; + QMap PostHooksJS; + QMap PostHandlersJS; + QMap ClientTunnels; QStringList addresses; struct ExtDockEntry { @@ -185,15 +191,20 @@ public: void ClearNotificationsStream(); void RegisterListenerConfig(const QString &name, const QString &protocol, const QString &type, const QString &ax_script); - void RegisterAgentConfig(const QString &agentName, const QString &ax_script, const QStringList &listenersconst, const bool &multiListeners); + void RegisterAgentConfig(const QString &agentName, const QString &ax_script, const QStringList &listenersconst, const bool &multiListeners, const QJsonArray &groups); void RegisterServiceConfig(const QString &serviceName, const QString &ax_script); - void ProcessAxScriptCommands(const QString &agentName, const QString &listenerType, int os, const QString &commandsJson); + void ProcessAxScriptPacket(const QString &name, const QString &content, const QJsonArray &groups); + void registerServerCommandGroups(const QString &scriptName, const QList &groups, QJSEngine* engine); + void EnableServerScript(const QString &name); + void DisableServerScript(const QString &name); + QList GetServerScripts() const; RegListenerConfig GetRegListener(const QString &listenerName); QList GetAgentNames(const QString &listenerType) const; RegAgentConfig GetRegAgent(const QString &agentName, const QString &listenerName, int os); AgentTypeInfo GetAgentTypeInfo(const QString &agentName) const; QList GetCommanders(const QStringList &listeners, const QStringList &agents, const QList &os) const; QList GetCommandersAll() const; + void AddCommandsToCommanders(const CommandsGroup &group, const QStringList &listeners, const QStringList &agents, const QList &os); void PostHookProcess(QJsonObject jsonHookObj); void PostHandlerProcess(const QString &handlerId, const TaskData &taskData); diff --git a/AdaptixClient/Headers/main.h b/AdaptixClient/Headers/main.h index 3e2749b7..6329961d 100644 --- a/AdaptixClient/Headers/main.h +++ b/AdaptixClient/Headers/main.h @@ -97,21 +97,21 @@ #define TYPE_SYNC_CATEGORY_BATCH 0x15 #define TYPE_CHAT_MESSAGE 0x18 +#define TYPE_SERVICE_DATA 0x19 -#define TYPE_SERVICE_REG 0x21 -#define TYPE_SERVICE_DATA 0x22 +#define TYPE_REG_LISTENER 0x21 +#define TYPE_REG_AGENT 0x22 +#define TYPE_REG_SERVICE 0x23 -#define TYPE_LISTENER_REG 0x31 -#define TYPE_LISTENER_START 0x32 +#define TYPE_LISTENER_START 0x31 +#define TYPE_LISTENER_EDIT 0x32 #define TYPE_LISTENER_STOP 0x33 -#define TYPE_LISTENER_EDIT 0x34 -#define TYPE_AGENT_REG 0x41 -#define TYPE_AGENT_NEW 0x42 -#define TYPE_AGENT_TICK 0x43 -#define TYPE_AGENT_UPDATE 0x44 -#define TYPE_AGENT_LINK 0x45 -#define TYPE_AGENT_REMOVE 0x46 +#define TYPE_AGENT_NEW 0x41 +#define TYPE_AGENT_UPDATE 0x42 +#define TYPE_AGENT_REMOVE 0x43 +#define TYPE_AGENT_TICK 0x44 +#define TYPE_AGENT_LINK 0x45 #define TYPE_AGENT_TASK_SYNC 0x49 #define TYPE_AGENT_TASK_UPDATE 0x4a diff --git a/AdaptixClient/Source/Agent/Commander.cpp b/AdaptixClient/Source/Agent/Commander.cpp index 871e2329..0fa9e17f 100644 --- a/AdaptixClient/Source/Agent/Commander.cpp +++ b/AdaptixClient/Source/Agent/Commander.cpp @@ -79,31 +79,134 @@ QStringList unserializeParams(const QString &commandline) Commander::Commander() { - regCommandsGroup = {}; - axCommandsGroup = {}; + mainCommandsGroup = {}; + serverGroups = {}; + clientGroups = {}; } Commander::~Commander() = default; -void Commander::AddRegCommands(const CommandsGroup &group) { regCommandsGroup = group; } +void Commander::SetAgentType(const QString &type) { agentType = type; } -void Commander::AddAxCommands(const CommandsGroup &group) +void Commander::SetMainCommands(const CommandsGroup &group) { mainCommandsGroup = group; } + +void Commander::AddServerGroup(const QString &scriptName, const QString &description, const CommandsGroup &group) { - axCommandsGroup.append(group); + ServerCommandsGroup sg; + sg.scriptName = scriptName; + sg.description = description; + sg.enabled = true; + sg.group = group; + serverGroups[scriptName] = sg; Q_EMIT commandsUpdated(); } -void Commander::RemoveAxCommands(const QString &filepath) +void Commander::RemoveServerGroup(const QString &scriptName) { - for (int i = 0; i < axCommandsGroup.size(); ++i) { - if (axCommandsGroup[i].filepath == filepath) { - axCommandsGroup.removeAt(i); + if (serverGroups.remove(scriptName) > 0) + Q_EMIT commandsUpdated(); +} + +void Commander::SetServerGroupEnabled(const QString &scriptName, bool enabled) +{ + if (!serverGroups.contains(scriptName)) + return; + if (serverGroups[scriptName].enabled == enabled) + return; + serverGroups[scriptName].enabled = enabled; + Q_EMIT commandsUpdated(); +} + +void Commander::SetServerGroupEngine(const QString &scriptName, QJSEngine* engine) +{ + if (!serverGroups.contains(scriptName)) + return; + serverGroups[scriptName].group.engine = engine; +} + +bool Commander::IsServerGroupEnabled(const QString &scriptName) const +{ + if (!serverGroups.contains(scriptName)) + return false; + return serverGroups[scriptName].enabled; +} + +QStringList Commander::GetServerGroupNames() const +{ + return serverGroups.keys(); +} + +ServerCommandsGroup Commander::GetServerGroup(const QString &scriptName) const +{ + return serverGroups.value(scriptName); +} + +void Commander::AddClientGroup(const CommandsGroup &group) +{ + clientGroups.append(group); + Q_EMIT commandsUpdated(); +} + +void Commander::RemoveClientGroup(const QString &filepath) +{ + for (int i = 0; i < clientGroups.size(); ++i) { + if (clientGroups[i].filepath == filepath) { + clientGroups.removeAt(i); i--; } } Q_EMIT commandsUpdated(); } +CommanderResult Commander::ProcessInputForGroup(const CommandsGroup &group, const QString &commandName, QStringList args, const QString &agentId, const QString &cmdline) +{ + for (const Command &command : group.commands) { + if (command.name != commandName) + continue; + + QJsonObject jsonObj; + jsonObj["command"] = command.name; + + if (command.subcommands.isEmpty()) { + auto cmdResult = ProcessCommand(command, "", args, jsonObj); + if (!cmdResult.output && command.is_pre_hook && group.engine && command.pre_hook.isCallable()) { + QString hook_result = ProcessPreHook(group.engine, command, agentId, cmdline, cmdResult.data, args); + if (hook_result.isEmpty()) + return CommanderResult{false, false, "", {}, true, {}}; + cmdResult.output = true; + cmdResult.error = true; + cmdResult.message = hook_result; + } + return cmdResult; + } + + if (args.isEmpty()) + return CommanderResult{true, true, "Subcommand must be set" + GenerateCommandHelp(command), {}, false, {}}; + + QString subCommandName = args[0]; + args.removeAt(0); + + for (const Command &subcommand : command.subcommands) { + if (subCommandName != subcommand.name) + continue; + + jsonObj["subcommand"] = subcommand.name; + auto cmdResult = ProcessCommand(subcommand, command.name, args, jsonObj); + if (!cmdResult.output && subcommand.is_pre_hook && group.engine && subcommand.pre_hook.isCallable()) { + QString hook_result = ProcessPreHook(group.engine, subcommand, agentId, cmdline, cmdResult.data, args); + if (hook_result.isEmpty()) + return CommanderResult{false, false, "", {}, true, {}}; + cmdResult.output = true; + cmdResult.error = true; + cmdResult.message = hook_result; + } + return cmdResult; + } + return CommanderResult{true, true, "Subcommand not found", {}, false, {}}; + } + return CommanderResult{false, false, "__not_found__", {}, false, {}}; +} + CommanderResult Commander::ProcessInput(QString agentId, QString cmdline) { QStringList parts = unserializeParams(cmdline); @@ -113,113 +216,27 @@ CommanderResult Commander::ProcessInput(QString agentId, QString cmdline) QString commandName = parts[0]; parts.removeAt(0); - if( commandName == "help") + if (commandName == "help") return this->ProcessHelp(parts); - for ( auto script_group : axCommandsGroup ) { - for (Command command : script_group.commands) { - if (command.name == commandName) { - QJsonObject jsonObj; - jsonObj["command"] = command.name; - - if ( command.subcommands.isEmpty() ) { - - auto cmdResult = ProcessCommand(command, "", parts, jsonObj); - if ( !cmdResult.output && command.is_pre_hook) { - QString hook_result = ProcessPreHook(script_group.engine, command, agentId, cmdline, cmdResult.data, parts); - if (hook_result.isEmpty()) { - return CommanderResult{false, false, "", {}, true, {} }; - } else { - cmdResult.output = true; - cmdResult.error = true; - cmdResult.message = hook_result; - } - } - return cmdResult; - - } - else { - if ( parts.isEmpty() ) - return CommanderResult{true, true, "Subcommand must be set" + GenerateCommandHelp(command), {}, false, {}}; - - QString subCommandName = parts[0]; - parts.removeAt(0); - - for (Command subcommand : command.subcommands) { - if (subCommandName == subcommand.name) { - jsonObj["subcommand"] = subcommand.name; - - auto cmdResult = ProcessCommand(subcommand, command.name, parts, jsonObj); - if ( !cmdResult.output && subcommand.is_pre_hook) { - QString hook_result = ProcessPreHook(script_group.engine, subcommand, agentId, cmdline, cmdResult.data, parts); - if (hook_result.isEmpty()) { - return CommanderResult{false, false, "", {}, true, {} }; - } else { - cmdResult.output = true; - cmdResult.error = true; - cmdResult.message = hook_result; - } - } - return cmdResult; - } - } - return CommanderResult{true, true, "Subcommand not found", {}, false, {}}; - } - } - } + for (const auto &client_group : clientGroups) { + auto result = ProcessInputForGroup(client_group, commandName, parts, agentId, cmdline); + if (result.message != "__not_found__") + return result; } - for (Command command : regCommandsGroup.commands) { - if (command.name == commandName) { - QJsonObject jsonObj; - jsonObj["command"] = command.name; - - if ( command.subcommands.isEmpty() ) { - - auto cmdResult = ProcessCommand(command, "", parts, jsonObj); - if ( !cmdResult.output && command.is_pre_hook) { - QString hook_result = ProcessPreHook(regCommandsGroup.engine, command, agentId, cmdline, cmdResult.data, parts); - if (hook_result.isEmpty()) { - return CommanderResult{false, false, "", {}, true, {} }; - } else { - cmdResult.output = true; - cmdResult.error = true; - cmdResult.message = hook_result; - } - } - return cmdResult; - - } else { - if ( parts.isEmpty() ) - return CommanderResult{true, true, "Subcommand must be set" + GenerateCommandHelp(command), {}, false, {} }; - - QString subCommandName = parts[0]; - parts.removeAt(0); - - for (Command subcommand : command.subcommands) { - if (subCommandName == subcommand.name) { - jsonObj["subcommand"] = subcommand.name; - - auto cmdResult = ProcessCommand(subcommand, command.name, parts, jsonObj); - if ( !cmdResult.output && subcommand.is_pre_hook) { - QString hook_result = ProcessPreHook(regCommandsGroup.engine, subcommand, agentId, cmdline, cmdResult.data, parts); - if (hook_result.isEmpty()) { - return CommanderResult{false, false, "", {}, true, {} }; - } else { - cmdResult.output = true; - cmdResult.error = true; - cmdResult.message = hook_result; - } - - } - return cmdResult; - } - } - return CommanderResult{true, true, "Subcommand not found", {}, false, {} }; - } - } + for (const auto &server_group : serverGroups) { + if (!server_group.enabled) + continue; + auto result = ProcessInputForGroup(server_group.group, commandName, parts, agentId, cmdline); + if (result.message != "__not_found__") + return result; } + auto result = ProcessInputForGroup(mainCommandsGroup, commandName, parts, agentId, cmdline); + if (result.message != "__not_found__") + return result; + return CommanderResult{true, true, "Command not found", {}, false, {}}; } @@ -367,15 +384,15 @@ CommanderResult Commander::ProcessCommand(const Command &command, const QString } } } else if (commandArg.required) { - if ( (commandArg.defaultValue.isNull() || !commandArg.defaultValue.isValid()) && !commandArg.defaultUsed) { + if (!commandArg.defaultUsed) { return CommanderResult{true, true, "Missing required argument: " + commandArg.name + GenerateCommandHelp(command, commandName), {}, false, {}}; } else { - if (commandArg.type == "STRING" && commandArg.defaultValue.typeId() == QMetaType::QString) { + if (commandArg.type == "STRING" && commandArg.defaultValue.canConvert()) { jsonObj[commandArg.name] = commandArg.defaultValue.toString(); - } else if (commandArg.type == "INT" && commandArg.defaultValue.typeId() == QMetaType::Int) { + } else if (commandArg.type == "INT" && commandArg.defaultValue.canConvert()) { jsonObj[commandArg.name] = commandArg.defaultValue.toInt(); - } else if (commandArg.type == "BOOL" && commandArg.defaultValue.typeId() == QMetaType::Bool) { + } else if (commandArg.type == "BOOL" && commandArg.defaultValue.canConvert()) { jsonObj[commandArg.mark] = commandArg.defaultValue.toBool(); } else { @@ -410,7 +427,7 @@ CommanderResult Commander::ProcessHelp(QStringList commandParts) output << QString(" Command Description\n"); output << QString(" ------- -----------\n"); - for (auto command : regCommandsGroup.commands) { + for (auto command : mainCommandsGroup.commands) { QString commandName = command.name; if (!command.subcommands.isEmpty()) commandName += '*'; @@ -419,19 +436,64 @@ CommanderResult Commander::ProcessHelp(QStringList commandParts) output << " " + commandName + tab + " " + command.description + "\n"; } - for ( auto script_group : axCommandsGroup ){ - output << QString("\n"); - output << QString(" Group - " + script_group.groupName + "\n"); - output << QString(" =====================================\n"); + for (const auto &server_group : serverGroups) { + if (!server_group.enabled) + continue; + if (server_group.group.groupName != agentType) + continue; - for ( auto command : script_group.commands ) { + for (const auto &command : server_group.group.commands) { QString commandName = command.name; - if ( command.subcommands.isEmpty() ) { + if (command.subcommands.isEmpty()) { QString tab = QString(TotalWidth - commandName.size(), ' '); output << " " + commandName + tab + " " + command.description + "\n"; + } else { + for (const auto &subcmd : command.subcommands) { + QString subcmdName = commandName + " " + subcmd.name; + QString tab = QString(TotalWidth - subcmdName.size(), ' '); + output << " " + subcmdName + tab + " " + subcmd.description + "\n"; + } } - else { - for ( auto subcmd : command.subcommands ) { + } + } + + for (const auto &server_group : serverGroups) { + if (!server_group.enabled) + continue; + if (server_group.group.groupName == agentType) + continue; + + output << QString("\n"); + output << QString(" Group - " + server_group.group.groupName + "\n"); + output << QString(" =====================================\n"); + + for (const auto &command : server_group.group.commands) { + QString commandName = command.name; + if (command.subcommands.isEmpty()) { + QString tab = QString(TotalWidth - commandName.size(), ' '); + output << " " + commandName + tab + " " + command.description + "\n"; + } else { + for (const auto &subcmd : command.subcommands) { + QString subcmdName = commandName + " " + subcmd.name; + QString tab = QString(TotalWidth - subcmdName.size(), ' '); + output << " " + subcmdName + tab + " " + subcmd.description + "\n"; + } + } + } + } + + for (const auto &client_group : clientGroups) { + output << QString("\n"); + output << QString(" Group - " + client_group.groupName + " (client)\n"); + output << QString(" =====================================\n"); + + for (const auto &command : client_group.commands) { + QString commandName = command.name; + if (command.subcommands.isEmpty()) { + QString tab = QString(TotalWidth - commandName.size(), ' '); + output << " " + commandName + tab + " " + command.description + "\n"; + } else { + for (const auto &subcmd : command.subcommands) { QString subcmdName = commandName + " " + subcmd.name; QString tab = QString(TotalWidth - subcmdName.size(), ' '); output << " " + subcmdName + tab + " " + subcmd.description + "\n"; @@ -446,18 +508,32 @@ CommanderResult Commander::ProcessHelp(QStringList commandParts) Command foundCommand; QString commandName = commandParts[0]; - for (Command cmd : regCommandsGroup.commands) { + for (Command cmd : mainCommandsGroup.commands) { if (cmd.name == commandName) { foundCommand = cmd; break; } } - for(auto script_group : axCommandsGroup) { + for (const auto &server_group : serverGroups) { + if ( !foundCommand.name.isEmpty() ) + break; + if (!server_group.enabled) + continue; + + for (Command cmd : server_group.group.commands) { + if (cmd.name == commandName) { + foundCommand = cmd; + break; + } + } + } + + for (const auto &client_group : clientGroups) { if ( !foundCommand.name.isEmpty() ) break; - for (Command cmd : script_group.commands) { + for (Command cmd : client_group.commands) { if (cmd.name == commandName) { foundCommand = cmd; break; @@ -558,39 +634,36 @@ CommanderResult Commander::ProcessHelp(QStringList commandParts) } } +static void collectCommandsFromGroup(const QList &commands, QStringList &cmdList, QStringList &helpList) +{ + for (const Command &cmd : commands) { + helpList << "help " + cmd.name; + if (cmd.subcommands.isEmpty()) { + cmdList << cmd.name; + } else { + for (const Command &subcmd : cmd.subcommands) { + cmdList << cmd.name + " " + subcmd.name; + helpList << "help " + cmd.name + " " + subcmd.name; + } + } + } +} + QStringList Commander::GetCommands() { QStringList commandList; QStringList helpCommandList; - for (Command cmd : regCommandsGroup.commands) { + collectCommandsFromGroup(mainCommandsGroup.commands, commandList, helpCommandList); - helpCommandList << "help " + cmd.name; - if (cmd.subcommands.isEmpty()) - commandList << cmd.name; - - for (Command subcmd : cmd.subcommands) { - commandList << cmd.name + " " + subcmd.name; - helpCommandList << "help " + cmd.name + " " + subcmd.name; - } + for (const auto &server_group : serverGroups) { + if (server_group.enabled) + collectCommandsFromGroup(server_group.group.commands, commandList, helpCommandList); } - for( auto script_group : axCommandsGroup) { - for (Command cmd : script_group.commands) { - - helpCommandList << "help " + cmd.name; - if (cmd.subcommands.isEmpty()) - commandList << cmd.name; - - for (Command subcmd : cmd.subcommands) { - commandList << cmd.name + " " + subcmd.name; - helpCommandList << "help " + cmd.name + " " + subcmd.name; - } - } - } - - for( QString cmd : helpCommandList) - commandList << cmd; + for (const auto &client_group : clientGroups) + collectCommandsFromGroup(client_group.commands, commandList, helpCommandList); + commandList << helpCommandList; return commandList; } diff --git a/AdaptixClient/Source/Client/AxScript/AxScriptEngine.cpp b/AdaptixClient/Source/Client/AxScript/AxScriptEngine.cpp index 87a8021b..0b2cf66a 100644 --- a/AdaptixClient/Source/Client/AxScript/AxScriptEngine.cpp +++ b/AdaptixClient/Source/Client/AxScript/AxScriptEngine.cpp @@ -105,6 +105,14 @@ BridgeMenu* AxScriptEngine::menu() const { return bridgeMenu.get(); } AxScriptManager* AxScriptEngine::manager() const { return this->scriptManager; } +void AxScriptEngine::setServerMode(bool enabled) { serverMode = enabled; } + +bool AxScriptEngine::isServerMode() const { return serverMode; } + +void AxScriptEngine::setEnabled(bool enabled) { scriptEnabled = enabled; } + +bool AxScriptEngine::isEnabled() const { return scriptEnabled; } + void AxScriptEngine::registerObject(QObject *obj) { context.objects.append(obj); } void AxScriptEngine::registerAction(QAction *action) { context.actions.append(action); } diff --git a/AdaptixClient/Source/Client/AxScript/AxScriptManager.cpp b/AdaptixClient/Source/Client/AxScript/AxScriptManager.cpp index f0b47a7d..0a42cdaa 100644 --- a/AdaptixClient/Source/Client/AxScript/AxScriptManager.cpp +++ b/AdaptixClient/Source/Client/AxScript/AxScriptManager.cpp @@ -46,27 +46,26 @@ void AxScriptManager::Clear() if (mainScript) { auto commanderList = adaptixWidget->GetCommandersAll(); for (const auto& commander : commanderList) - commander->RemoveAxCommands(mainScript->context.name); + commander->RemoveClientGroup(mainScript->context.name); delete mainScript; mainScript = nullptr; } - qDeleteAll(services_scripts); - services_scripts.clear(); - qDeleteAll(agents_scripts); - agents_scripts.clear(); - qDeleteAll(listeners_scripts); - listeners_scripts.clear(); + for (auto &entry : config_scripts) + delete entry.engine; + config_scripts.clear(); qDeleteAll(scripts); scripts.clear(); + qDeleteAll(server_scripts); + server_scripts.clear(); } void AxScriptManager::ResetMain() { auto commanderList = adaptixWidget->GetCommandersAll(); for (const auto& commander : commanderList) - commander->RemoveAxCommands(mainScript->context.name); + commander->RemoveClientGroup(mainScript->context.name); if (mainScript) delete mainScript; @@ -76,15 +75,12 @@ void AxScriptManager::ResetMain() QJSEngine* AxScriptManager::GetEngine(const QString &name) { - if (agents_scripts.contains(name) && agents_scripts[name]) - return agents_scripts[name]->engine(); + if (config_scripts.contains(name) && config_scripts[name].engine) + return config_scripts[name].engine->engine(); if (scripts.contains(name) && scripts[name]) return scripts[name]->engine(); - if (services_scripts.contains(name) && services_scripts[name]) - return services_scripts[name]->engine(); - if (name == "main" && mainScript) return mainScript->engine(); @@ -129,59 +125,66 @@ QVector AxScriptManager::GetTunnels() const { QStringList AxScriptManager::GetInterfaces() const { return adaptixWidget->addresses; } -/// MAIN +/// CONFIG SCRIPTS (Listener, Agent, Service) -QStringList AxScriptManager::ListenerScriptList() { return listeners_scripts.keys(); } +static QStringList configScriptListByType(const QMap &map, ConfigScriptType type) +{ + QStringList result; + for (auto it = map.begin(); it != map.end(); ++it) { + if (it.value().type == type) + result.append(it.key()); + } + return result; +} + +QStringList AxScriptManager::ListenerScriptList() { return configScriptListByType(config_scripts, ConfigScriptType::Listener); } void AxScriptManager::ListenerScriptAdd(const QString &name, const QString &ax_script) { - if (listeners_scripts.contains(name)) + if (config_scripts.contains(name)) return; AxScriptEngine* script = new AxScriptEngine(this, name, this); script->execute(ax_script); - - listeners_scripts[name] = script; + config_scripts[name] = {ConfigScriptType::Listener, script}; } QJSEngine* AxScriptManager::ListenerScriptEngine(const QString &name) { - if (!listeners_scripts.contains(name)) return nullptr; - return listeners_scripts[name]->engine(); + if (!config_scripts.contains(name) || config_scripts[name].type != ConfigScriptType::Listener) + return nullptr; + return config_scripts[name].engine->engine(); } - - -QStringList AxScriptManager::AgentScriptList() { return agents_scripts.keys(); } +QStringList AxScriptManager::AgentScriptList() { return configScriptListByType(config_scripts, ConfigScriptType::Agent); } void AxScriptManager::AgentScriptAdd(const QString &name, const QString &ax_script) { - if (agents_scripts.contains(name)) return; + if (config_scripts.contains(name)) + return; AxScriptEngine* script = new AxScriptEngine(this, name, this); script->execute(ax_script); - - agents_scripts[name] = script; + config_scripts[name] = {ConfigScriptType::Agent, script}; } QJSEngine* AxScriptManager::AgentScriptEngine(const QString &name) { - if (!agents_scripts.contains(name)) return nullptr; - return agents_scripts[name]->engine(); + if (!config_scripts.contains(name) || config_scripts[name].type != ConfigScriptType::Agent) + return nullptr; + return config_scripts[name].engine->engine(); } - - -QStringList AxScriptManager::ServiceScriptList() { return services_scripts.keys(); } +QStringList AxScriptManager::ServiceScriptList() { return configScriptListByType(config_scripts, ConfigScriptType::Service); } void AxScriptManager::ServiceScriptAdd(const QString &name, const QString &ax_script) { - if (services_scripts.contains(name)) return; + if (config_scripts.contains(name)) + return; AxScriptEngine* script = new AxScriptEngine(this, name, this); script->execute(ax_script); - - services_scripts[name] = script; + config_scripts[name] = {ConfigScriptType::Service, script}; QJSValue func = script->engine()->globalObject().property("InitService"); if (func.isCallable()) @@ -190,37 +193,28 @@ void AxScriptManager::ServiceScriptAdd(const QString &name, const QString &ax_sc QJSEngine* AxScriptManager::ServiceScriptEngine(const QString &name) { - if (!services_scripts.contains(name)) return nullptr; - return services_scripts[name]->engine(); + if (!config_scripts.contains(name) || config_scripts[name].type != ConfigScriptType::Service) + return nullptr; + return config_scripts[name].engine->engine(); } void AxScriptManager::ServiceScriptDataHandler(const QString &name, const QString &data) { - if (!services_scripts.contains(name)) return; - - QJSValue func = services_scripts[name]->engine()->globalObject().property("data_handler"); - if (!func.isCallable()) { - // consolePrintError(name + " - function data_handler is not registered"); + if (!config_scripts.contains(name) || config_scripts[name].type != ConfigScriptType::Service) return; - } - QJSValueList args; - args << QJSValue(data); - func.call(args); - // QJSValue result = func.call(args); - // if (result.isError()) { - // QString error = QStringLiteral("%1\n at line %2 in %3\n stack: %4").arg(result.toString()).arg(result.property("lineNumber").toInt()).arg(name).arg(result.property("stack").toString()); - // consolePrintError(error); - // } + QJSValue func = config_scripts[name].engine->engine()->globalObject().property("data_handler"); + if (!func.isCallable()) + return; + + func.call(QJSValueList() << QJSValue(data)); } - - QJSValue AxScriptManager::AgentScriptExecute(const QString &name, const QString &code) { QJSValue result; - if (agents_scripts.contains(name)) { - QJSValue func = agents_scripts[name]->engine()->globalObject().property(code); + if (config_scripts.contains(name) && config_scripts[name].type == ConfigScriptType::Agent) { + QJSValue func = config_scripts[name].engine->engine()->globalObject().property(code); if (func.isCallable()) { QJSValueList args; args << QJSValue("BeaconHTTP"); @@ -260,12 +254,93 @@ void AxScriptManager::ScriptRemove(const ExtensionFile &ext) auto commanderList = adaptixWidget->GetCommandersAll(); for (const auto& commander : commanderList) - commander->RemoveAxCommands(ext.FilePath); + commander->RemoveClientGroup(ext.FilePath); delete scriptEngine; } +void AxScriptManager::ServerScriptAdd(const ServerScriptData &data) +{ + if (server_scripts.contains(data.name)) { + delete server_scripts.take(data.name); + } + ServerScriptData scriptData = data; + scriptData.enabled = true; + + if (!data.code.isEmpty()) { + AxScriptEngine* scriptEngine = new AxScriptEngine(this, "__server__:" + data.name, this); + scriptEngine->setServerMode(true); + scriptEngine->execute(data.code); + server_scripts[data.name] = scriptEngine; + + QJSValue metadata = scriptEngine->engine()->globalObject().property("metadata"); + if (metadata.isObject() && scriptData.description.isEmpty()) + scriptData.description = metadata.property("description").toString(); + } + + server_scripts_data[data.name] = scriptData; +} + +void AxScriptManager::ServerScriptRemove(const QString &name) +{ + if (server_scripts.contains(name)) { + delete server_scripts.take(name); + } + server_scripts_data.remove(name); +} + +void AxScriptManager::ServerScriptSetEnabled(const QString &name, bool enabled) +{ + if (!server_scripts_data.contains(name)) + return; + + if (server_scripts_data[name].enabled == enabled) + return; + + server_scripts_data[name].enabled = enabled; + + if (enabled) { + if (server_scripts.contains(name)) + return; + + const QString &code = server_scripts_data[name].code; + if (!code.isEmpty()) { + AxScriptEngine* scriptEngine = new AxScriptEngine(this, "__server__:" + name, this); + scriptEngine->setServerMode(true); + scriptEngine->execute(code); + server_scripts[name] = scriptEngine; + } + } else { + if (server_scripts.contains(name)) { + delete server_scripts.take(name); + } + } +} + +bool AxScriptManager::ServerScriptIsEnabled(const QString &name) const +{ + if (!server_scripts_data.contains(name)) + return false; + return server_scripts_data[name].enabled; +} + +QJSEngine* AxScriptManager::ServerScriptEngine(const QString &name) +{ + if (!server_scripts.contains(name)) + return nullptr; + return server_scripts[name]->engine(); +} + +QList AxScriptManager::ServerScriptList() const +{ + return server_scripts_data.values(); +} + +ServerScriptData AxScriptManager::ServerScriptGet(const QString &name) const +{ + return server_scripts_data.value(name); +} void AxScriptManager::GlobalScriptLoad(const QString &path) { Q_EMIT adaptixWidget->LoadGlobalScriptSignal(path); } @@ -324,8 +399,13 @@ void AxScriptManager::ExecuteSmart(const QString& code, const QString& name) QList AxScriptManager::getAllEngines() const { QList list; - list.reserve(agents_scripts.size() + services_scripts.size() + scripts.size() + 1); - list << agents_scripts.values() << services_scripts.values() << scripts.values(); + list.reserve(config_scripts.size() + scripts.size() + server_scripts.size() + 1); + + for (const auto &entry : config_scripts) + list << entry.engine; + + list << scripts.values() << server_scripts.values(); + if (mainScript) list << mainScript; return list; @@ -386,9 +466,7 @@ int AxScriptManager::addMenuItemsToMenu(QMenu* menu, const QList& it void AxScriptManager::RegisterCommandsGroup(const CommandsGroup &group, const QStringList &listeners, const QStringList &agents, const QList &os) { - auto commanderList = adaptixWidget->GetCommanders(listeners, agents, os); - for (const auto& commander : commanderList) - commander->AddAxCommands(group); + adaptixWidget->AddCommandsToCommanders(group, listeners, agents, os); } QStringList AxScriptManager::EventList() diff --git a/AdaptixClient/Source/Client/AxScript/BridgeApp.cpp b/AdaptixClient/Source/Client/AxScript/BridgeApp.cpp index 129feed1..45659ef5 100644 --- a/AdaptixClient/Source/Client/AxScript/BridgeApp.cpp +++ b/AdaptixClient/Source/Client/AxScript/BridgeApp.cpp @@ -1063,6 +1063,9 @@ int BridgeApp::random_int(const int min, const int max) { return GenerateRandomI void BridgeApp::register_commands_group(QObject *obj, const QJSValue &agents, const QJSValue &os, const QJSValue &listeners) { + if (scriptEngine->isServerMode()) + return; + if (!AxScriptUtils::isValidArray(agents)) { Q_EMIT engineError("register_commands_group expected array of strings in agents parameter!"); return; @@ -1100,6 +1103,9 @@ void BridgeApp::register_commands_group(QObject *obj, const QJSValue &agents, co void BridgeApp::script_import(const QString &path) { + if (scriptEngine->isServerMode()) + return; + QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return scriptEngine->engine()->throwError("Could not open script: " + path); @@ -1111,9 +1117,21 @@ void BridgeApp::script_import(const QString &path) scriptEngine->engine()->evaluate(code, path); } -void BridgeApp::script_load(const QString &path) { scriptEngine->manager()->GlobalScriptLoad(path); } +void BridgeApp::script_load(const QString &path) +{ + if (scriptEngine->isServerMode()) + return; -void BridgeApp::script_unload(const QString &path) { scriptEngine->manager()->GlobalScriptUnload(path); } + scriptEngine->manager()->GlobalScriptLoad(path); +} + +void BridgeApp::script_unload(const QString &path) +{ + if (scriptEngine->isServerMode()) + return; + + scriptEngine->manager()->GlobalScriptUnload(path); +} QString BridgeApp::script_dir() { return GetParentPathUnix(scriptEngine->context.name) + "/"; } diff --git a/AdaptixClient/Source/Client/Extender.cpp b/AdaptixClient/Source/Client/Extender.cpp index 88ba5999..8086a95d 100644 --- a/AdaptixClient/Source/Client/Extender.cpp +++ b/AdaptixClient/Source/Client/Extender.cpp @@ -17,6 +17,10 @@ void Extender::LoadFromDB() { auto list = mainAdaptix->storage->ListExtensions(); for(auto ext : list) { + if (ext.FilePath.startsWith("__server__:")) { + mainAdaptix->storage->RemoveExtension(ext.FilePath); + continue; + } QFile file(ext.FilePath); if (!file.open(QIODevice::ReadOnly)) { diff --git a/AdaptixClient/Source/Client/HttpRequestManager.cpp b/AdaptixClient/Source/Client/HttpRequestManager.cpp index e3566442..077806a4 100644 --- a/AdaptixClient/Source/Client/HttpRequestManager.cpp +++ b/AdaptixClient/Source/Client/HttpRequestManager.cpp @@ -39,6 +39,7 @@ QNetworkRequest HttpRequestManager::createRequest(const QString& url, const QStr QNetworkRequest request{QUrl(url)}; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setSslConfiguration(m_sslConfig); + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); if (!accessToken.isEmpty()) { QString bearerToken = "Bearer " + accessToken; @@ -252,6 +253,7 @@ void HttpRequestManager::cleanupRequest(int requestId) if (m_pendingRequests.contains(requestId)) { QNetworkReply* reply = m_pendingRequests[requestId].reply; if (reply) { + disconnect(reply, nullptr, this, nullptr); m_replyToRequestId.remove(reply); } m_pendingRequests.remove(requestId); diff --git a/AdaptixClient/Source/Client/ProcessSyncPacket.cpp b/AdaptixClient/Source/Client/ProcessSyncPacket.cpp index 5d4df6a5..0eaabc7d 100644 --- a/AdaptixClient/Source/Client/ProcessSyncPacket.cpp +++ b/AdaptixClient/Source/Client/ProcessSyncPacket.cpp @@ -187,7 +187,7 @@ bool AdaptixWidget::isValidSyncPacket(QJsonObject jsonObj) checkField("date", isNum) && checkField("message", isStr); - case TYPE_SERVICE_REG: + case TYPE_REG_SERVICE: return checkField("service", isStr) && checkField("ax", isStr); @@ -195,7 +195,7 @@ bool AdaptixWidget::isValidSyncPacket(QJsonObject jsonObj) return checkField("service", isStr) && checkField("data", isStr); - case TYPE_LISTENER_REG: + case TYPE_REG_LISTENER: return checkField("l_name", isStr) && checkField("l_protocol", isStr) && checkField("l_type", isStr) && @@ -217,11 +217,12 @@ bool AdaptixWidget::isValidSyncPacket(QJsonObject jsonObj) case TYPE_LISTENER_STOP: return checkField("l_name", isStr); - case TYPE_AGENT_REG: + case TYPE_REG_AGENT: return checkField("agent", isStr) && checkField("ax", isStr) && - checkField("listeners", isArr); - checkField("multi_listeners", isBl); + checkField("listeners", isArr) && + checkField("multi_listeners", isBl) && + checkField("groups", isArr); case TYPE_AGENT_NEW: return checkField("a_id", isStr) && @@ -497,10 +498,9 @@ bool AdaptixWidget::isValidSyncPacket(QJsonObject jsonObj) case TYPE_AXSCRIPT_COMMANDS: - return checkField("agent", isStr) && - checkField("listener", isStr) && - checkField("os", isNum) && - checkField("commands", isStr); + return checkField("name", isStr) && + checkField("content", isStr) && + checkField("groups", isArr); default: qWarning() << "[SyncPacket] Unknown packet type:" << spType; @@ -538,8 +538,9 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) int count = jsonObj["count"].toDouble(); QJsonArray interfaces = jsonObj["interfaces"].toArray(); this->addresses.clear(); - for (const QJsonValue &addrValue : interfaces) + for (const QJsonValue &addrValue : interfaces) { this->addresses.append(addrValue.toString()); + } if (count <= 0) { this->sync = false; @@ -588,7 +589,7 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) ListenersDock->RemoveListenerItem(jsonObj["l_name"].toString()); break; - case TYPE_SERVICE_REG: + case TYPE_REG_SERVICE: this->RegisterServiceConfig( jsonObj["service"].toString(), jsonObj["ax"].toString() ); break; @@ -596,13 +597,8 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) ScriptManager->ServiceScriptDataHandler( jsonObj["service"].toString(), jsonObj["data"].toString() ); break; - case TYPE_LISTENER_REG: - this->RegisterListenerConfig( - jsonObj["l_name"].toString(), - jsonObj["l_protocol"].toString(), - jsonObj["l_type"].toString(), - jsonObj["ax"].toString() - ); + case TYPE_REG_LISTENER: + this->RegisterListenerConfig( jsonObj["l_name"].toString(), jsonObj["l_protocol"].toString(), jsonObj["l_type"].toString(), jsonObj["ax"].toString() ); break; case TYPE_AGENT_NEW: { @@ -666,11 +662,11 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) break; } - case TYPE_AGENT_REG: { + case TYPE_REG_AGENT: { QStringList listeners; for (const QJsonValue &listener : jsonObj["listeners"].toArray()) listeners.append(listener.toString()); - this->RegisterAgentConfig(jsonObj["agent"].toString(), jsonObj["ax"].toString(), listeners, jsonObj["multi_listeners"].toBool()); + this->RegisterAgentConfig(jsonObj["agent"].toString(), jsonObj["ax"].toString(), listeners, jsonObj["multi_listeners"].toBool(), jsonObj["groups"].toArray()); break; } @@ -764,19 +760,11 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) qint64 startTime = jsonObj["a_start_time"].toDouble(); qint64 finishTime = jsonObj["a_finish_time"].toDouble(); bool completed = jsonObj["a_completed"].toBool(); - AgentsMap[agentId]->Console->ConsoleOutputPrompt( - startTime, jsonObj["a_task_id"].toString(), - jsonObj["a_client"].toString(), jsonObj["a_cmdline"].toString() - ); - if (!this->synchronized) + AgentsMap[agentId]->Console->ConsoleOutputPrompt( startTime, jsonObj["a_task_id"].toString(), jsonObj["a_client"].toString(), jsonObj["a_cmdline"].toString() ); + if (!this->synchronized) { AgentsMap[agentId]->Console->AddToHistory(jsonObj["a_cmdline"].toString()); - AgentsMap[agentId]->Console->ConsoleOutputMessage( - completed ? finishTime : startTime, - jsonObj["a_task_id"].toString(), - jsonObj["a_msg_type"].toDouble(), - jsonObj["a_message"].toString(), - jsonObj["a_text"].toString(), completed - ); + } + AgentsMap[agentId]->Console->ConsoleOutputMessage( completed ? finishTime : startTime, jsonObj["a_task_id"].toString(), jsonObj["a_msg_type"].toDouble(), jsonObj["a_message"].toString(), jsonObj["a_text"].toString(), completed ); } break; } @@ -784,25 +772,13 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) case TYPE_AGENT_CONSOLE_TASK_UPD: { QString agentId = jsonObj["a_id"].toString(); QReadLocker locker(&AgentsMapLock); - if (AgentsMap.contains(agentId)) { - AgentsMap[agentId]->Console->ConsoleOutputMessage( - jsonObj["a_finish_time"].toDouble(), - jsonObj["a_task_id"].toString(), - jsonObj["a_msg_type"].toDouble(), - jsonObj["a_message"].toString(), - jsonObj["a_text"].toString(), - jsonObj["a_completed"].toBool() - ); - } + if (AgentsMap.contains(agentId)) + AgentsMap[agentId]->Console->ConsoleOutputMessage( jsonObj["a_finish_time"].toDouble(), jsonObj["a_task_id"].toString(), jsonObj["a_msg_type"].toDouble(), jsonObj["a_message"].toString(), jsonObj["a_text"].toString(), jsonObj["a_completed"].toBool() ); break; } case TYPE_CHAT_MESSAGE: - ChatDock->AddChatMessage( - jsonObj["c_date"].toDouble(), - jsonObj["c_username"].toString(), - jsonObj["c_message"].toString() - ); + ChatDock->AddChatMessage( jsonObj["c_date"].toDouble(), jsonObj["c_username"].toString(), jsonObj["c_message"].toString() ); break; case TYPE_DOWNLOAD_CREATE: @@ -810,17 +786,14 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) break; case TYPE_DOWNLOAD_UPDATE: - DownloadsDock->EditDownloadItem( - jsonObj["d_file_id"].toString(), - static_cast(jsonObj["d_recv_size"].toDouble()), - static_cast(jsonObj["d_state"].toDouble()) - ); + DownloadsDock->EditDownloadItem( jsonObj["d_file_id"].toString(), static_cast(jsonObj["d_recv_size"].toDouble()), static_cast(jsonObj["d_state"].toDouble()) ); break; case TYPE_DOWNLOAD_DELETE: { QStringList ids; - for (const QJsonValue &val : jsonObj["d_files_id"].toArray()) + for (const QJsonValue &val : jsonObj["d_files_id"].toArray()) { if (val.isString()) ids.append(val.toString()); + } DownloadsDock->RemoveDownloadItem(ids); break; } @@ -834,10 +807,7 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) break; case TYPE_SCREEN_UPDATE: - ScreenshotsDock->EditScreenshotItem( - jsonObj["s_screen_id"].toString(), - jsonObj["s_note"].toString() - ); + ScreenshotsDock->EditScreenshotItem( jsonObj["s_screen_id"].toString(), jsonObj["s_note"].toString() ); break; case TYPE_SCREEN_DELETE: @@ -847,7 +817,8 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) case TYPE_CREDS_CREATE: { QList credList; for (const QJsonValue &val : jsonObj["c_creds"].toArray()) { - if (!val.isObject()) continue; + if (!val.isObject()) + continue; QJsonObject obj = val.toObject(); if (!obj.contains("c_creds_id") || !obj["c_creds_id"].isString()) continue; if (!obj.contains("c_username") || !obj["c_username"].isString()) continue; @@ -894,16 +865,19 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) case TYPE_CREDS_DELETE: { QStringList ids; - for (const QJsonValue &val : jsonObj["c_creds_id"].toArray()) - if (val.isString()) ids.append(val.toString()); + for (const QJsonValue &val : jsonObj["c_creds_id"].toArray()) { + if (val.isString()) + ids.append(val.toString()); + } CredentialsDock->RemoveCredentialsItem(ids); break; } case TYPE_CREDS_SET_TAG: { QStringList ids; - for (const QJsonValue &val : jsonObj["c_creds_id"].toArray()) + for (const QJsonValue &val : jsonObj["c_creds_id"].toArray()) { ids.append(val.toString()); + } CredentialsDock->CredsSetTag(ids, jsonObj["c_tag"].toString()); break; } @@ -911,7 +885,8 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) case TYPE_TARGETS_CREATE: { QList targetsList; for (const QJsonValue &val : jsonObj["t_targets"].toArray()) { - if (!val.isObject()) continue; + if (!val.isObject()) + continue; QJsonObject obj = val.toObject(); if (!obj.contains("t_target_id") || !obj["t_target_id"].isString()) continue; if (!obj.contains("t_computer") || !obj["t_computer"].isString()) continue; @@ -938,8 +913,9 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) t.Date = UnixTimestampGlobalToStringLocal(t.DateTimestamp); t.Alive = obj["t_alive"].toBool(); if (obj["t_agents"].isArray()) { - for (const QJsonValue &aid : obj["t_agents"].toArray()) + for (const QJsonValue &aid : obj["t_agents"].toArray()) { if (aid.isString()) t.Agents.append(aid.toString()); + } } t.OsIcon = getTargetOsIcon(t.Os, !t.Agents.isEmpty(), t.Alive); targetsList.append(t); @@ -962,8 +938,9 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) t.Date = UnixTimestampGlobalToStringLocal(t.DateTimestamp); t.Alive = jsonObj["t_alive"].toBool(); if (jsonObj["t_agents"].isArray()) { - for (const QJsonValue &aid : jsonObj["t_agents"].toArray()) + for (const QJsonValue &aid : jsonObj["t_agents"].toArray()) { if (aid.isString()) t.Agents.append(aid.toString()); + } } t.OsIcon = getTargetOsIcon(t.Os, !t.Agents.isEmpty(), t.Alive); TargetsDock->EditTargetsItem(t); @@ -972,16 +949,19 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) case TYPE_TARGETS_DELETE: { QStringList ids; - for (const QJsonValue &val : jsonObj["t_target_id"].toArray()) - if (val.isString()) ids.append(val.toString()); + for (const QJsonValue &val : jsonObj["t_target_id"].toArray()) { + if (val.isString()) + ids.append(val.toString()); + } TargetsDock->RemoveTargetsItem(ids); break; } case TYPE_TARGETS_SET_TAG: { QStringList ids; - for (const QJsonValue &val : jsonObj["t_targets_id"].toArray()) + for (const QJsonValue &val : jsonObj["t_targets_id"].toArray()) { ids.append(val.toString()); + } TargetsDock->TargetsSetTag(ids, jsonObj["t_tag"].toString()); break; } @@ -1002,13 +982,9 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) QString agentId = jsonObj["b_agent_id"].toString(); if (AgentsMap.contains(agentId)) { auto agent = AgentsMap[agentId]; - if (agent && agent->HasFileBrowser()) - agent->GetFileBrowser()->SetDisksWin( - jsonObj["b_time"].toDouble(), - jsonObj["b_msg_type"].toDouble(), - jsonObj["b_message"].toString(), - jsonObj["b_data"].toString() - ); + if (agent && agent->HasFileBrowser()) { + agent->GetFileBrowser()->SetDisksWin( jsonObj["b_time"].toDouble(), jsonObj["b_msg_type"].toDouble(), jsonObj["b_message"].toString(), jsonObj["b_data"].toString() ); + } } break; } @@ -1017,14 +993,9 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) QString agentId = jsonObj["b_agent_id"].toString(); if (AgentsMap.contains(agentId)) { auto agent = AgentsMap[agentId]; - if (agent && agent->HasFileBrowser()) - agent->GetFileBrowser()->AddFiles( - jsonObj["b_time"].toDouble(), - jsonObj["b_msg_type"].toDouble(), - jsonObj["b_message"].toString(), - jsonObj["b_path"].toString(), - jsonObj["b_data"].toString() - ); + if (agent && agent->HasFileBrowser()){ + agent->GetFileBrowser()->AddFiles( jsonObj["b_time"].toDouble(), jsonObj["b_msg_type"].toDouble(), jsonObj["b_message"].toString(), jsonObj["b_path"].toString(), jsonObj["b_data"].toString() ); + } } break; } @@ -1034,15 +1005,8 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) if (AgentsMap.contains(agentId)) { auto agent = AgentsMap[agentId]; if (agent && agent->HasProcessBrowser()) { - agent->GetProcessBrowser()->SetStatus( - jsonObj["b_time"].toDouble(), - jsonObj["b_msg_type"].toDouble(), - jsonObj["b_message"].toString() - ); - agent->GetProcessBrowser()->SetProcess( - jsonObj["b_msg_type"].toDouble(), - jsonObj["b_data"].toString() - ); + agent->GetProcessBrowser()->SetStatus( jsonObj["b_time"].toDouble(), jsonObj["b_msg_type"].toDouble(), jsonObj["b_message"].toString() ); + agent->GetProcessBrowser()->SetProcess( jsonObj["b_msg_type"].toDouble(), jsonObj["b_data"].toString() ); } } break; @@ -1053,11 +1017,7 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) if (AgentsMap.contains(agentId)) { auto agent = AgentsMap[agentId]; if (agent && agent->HasFileBrowser()) - agent->GetFileBrowser()->SetStatus( - jsonObj["b_time"].toDouble(), - jsonObj["b_msg_type"].toDouble(), - jsonObj["b_message"].toString() - ); + agent->GetFileBrowser()->SetStatus( jsonObj["b_time"].toDouble(), jsonObj["b_msg_type"].toDouble(), jsonObj["b_message"].toString() ); } break; } @@ -1094,20 +1054,11 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) } case TYPE_AXSCRIPT_COMMANDS: - this->ProcessAxScriptCommands( - jsonObj["agent"].toString(), - jsonObj["listener"].toString(), - static_cast(jsonObj["os"].toDouble()), - jsonObj["commands"].toString() - ); + this->ProcessAxScriptPacket(jsonObj["name"].toString(), jsonObj["content"].toString(), jsonObj["groups"].toArray()); break; case SP_TYPE_EVENT: - LogsDock->AddLogs( - jsonObj["event_type"].toDouble(), - jsonObj["date"].toDouble(), - jsonObj["message"].toString() - ); + LogsDock->AddLogs( jsonObj["event_type"].toDouble(), jsonObj["date"].toDouble(), jsonObj["message"].toString() ); break; default: diff --git a/AdaptixClient/Source/Client/Requestor.cpp b/AdaptixClient/Source/Client/Requestor.cpp index fe8464ea..202b6f2d 100644 --- a/AdaptixClient/Source/Client/Requestor.cpp +++ b/AdaptixClient/Source/Client/Requestor.cpp @@ -13,6 +13,7 @@ QJsonObject HttpReq(const QString &sUrl, const QByteArray &jsonData, const QStri QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setSslConfiguration(sslConfig); + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); if( !token.isEmpty() ) { QString bearerToken = "Bearer " + token; request.setRawHeader("Authorization", bearerToken.toUtf8()); @@ -457,3 +458,34 @@ void HttpReqServiceCallAsync(const QString &service, const QString &command, con HttpRequestManager::instance().post(profile.GetURL(), "/service/call", profile.GetAccessToken(), jsonData, callback); } + +void HttpReqAxScriptListAsync(AuthProfile& profile, const HttpCallback &callback) +{ + QByteArray jsonData = QJsonDocument(QJsonObject()).toJson(); + HttpRequestManager::instance().post(profile.GetURL(), "/axscript/list", profile.GetAccessToken(), jsonData, callback); +} + +void HttpReqAxScriptCommandsAsync(AuthProfile& profile, const HttpCallback &callback) +{ + QByteArray jsonData = QJsonDocument(QJsonObject()).toJson(); + HttpRequestManager::instance().post(profile.GetURL(), "/axscript/commands", profile.GetAccessToken(), jsonData, callback); +} + +void HttpReqAxScriptLoadAsync(const QString &name, const QString &script, AuthProfile& profile, const HttpCallback &callback) +{ + QJsonObject dataJson; + dataJson["name"] = name; + dataJson["script"] = script; + QByteArray jsonData = QJsonDocument(dataJson).toJson(); + + HttpRequestManager::instance().post(profile.GetURL(), "/axscript/load", profile.GetAccessToken(), jsonData, callback); +} + +void HttpReqAxScriptUnloadAsync(const QString &name, AuthProfile& profile, const HttpCallback &callback) +{ + QJsonObject dataJson; + dataJson["name"] = name; + QByteArray jsonData = QJsonDocument(dataJson).toJson(); + + HttpRequestManager::instance().post(profile.GetURL(), "/axscript/unload", profile.GetAccessToken(), jsonData, callback); +} diff --git a/AdaptixClient/Source/UI/Dialogs/DialogExtender.cpp b/AdaptixClient/Source/UI/Dialogs/DialogExtender.cpp index 6925fce8..003c139a 100644 --- a/AdaptixClient/Source/UI/Dialogs/DialogExtender.cpp +++ b/AdaptixClient/Source/UI/Dialogs/DialogExtender.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -12,40 +13,48 @@ DialogExtender::DialogExtender(Extender* e) this->createUI(); - connect(tableWidget, &QTableWidget::customContextMenuRequested, this, &DialogExtender::handleMenu); - connect(tableWidget, &QTableWidget::cellClicked, this, &DialogExtender::onRowSelect); - connect(buttonClose, &QPushButton::clicked, this, &DialogExtender::close); + connect(tableWidget, &QTableWidget::customContextMenuRequested, this, &DialogExtender::handleMenu); + connect(tableWidget, &QTableWidget::cellClicked, this, &DialogExtender::onRowSelect); + connect(serverTableWidget, &QTableWidget::customContextMenuRequested, this, &DialogExtender::handleServerMenu); + connect(serverTableWidget, &QTableWidget::cellClicked, this, &DialogExtender::onServerRowSelect); + connect(serverProjectCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &DialogExtender::onProjectChanged); + connect(buttonClose, &QPushButton::clicked, this, &DialogExtender::close); } DialogExtender::~DialogExtender() = default; +static QTableWidget* createScriptTable(QWidget* parent, int columnCount) +{ + auto* table = new QTableWidget(parent); + table->setColumnCount(columnCount); + table->setContextMenuPolicy(Qt::CustomContextMenu); + table->setAutoFillBackground(false); + table->setShowGrid(false); + table->setSortingEnabled(true); + table->setWordWrap(true); + table->setCornerButtonEnabled(false); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setFocusPolicy(Qt::NoFocus); + table->setAlternatingRowColors(true); + table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + table->horizontalHeader()->setCascadingSectionResizes(true); + table->horizontalHeader()->setHighlightSections(false); + table->verticalHeader()->setVisible(false); + return table; +} + void DialogExtender::createUI() { this->setWindowTitle("AxScript manager"); this->resize(1200, 700); this->setProperty("Main", "base"); - tableWidget = new QTableWidget(this); - tableWidget->setColumnCount(4); - tableWidget->setContextMenuPolicy(Qt::CustomContextMenu ); - tableWidget->setAutoFillBackground(false); - tableWidget->setShowGrid(false); - tableWidget->setSortingEnabled(true); - tableWidget->setWordWrap(true); - tableWidget->setCornerButtonEnabled(false); - tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows ); - tableWidget->setFocusPolicy(Qt::NoFocus ); - tableWidget->setAlternatingRowColors(true); - tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - tableWidget->horizontalHeader()->setCascadingSectionResizes(true); - tableWidget->horizontalHeader()->setHighlightSections(false); - tableWidget->verticalHeader()->setVisible(false); - - tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("Name" ) ); - tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("Path" ) ); - tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("Status" ) ); - tableWidget->setHorizontalHeaderItem(3, new QTableWidgetItem("Description" ) ); - + /// Local Scripts + tableWidget = createScriptTable(this, 4); + tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("Name")); + tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("Path")); + tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("Status")); + tableWidget->setHorizontalHeaderItem(3, new QTableWidgetItem("Description")); tableWidget->hideColumn(3); textComment = new QTextEdit(this); @@ -54,11 +63,43 @@ void DialogExtender::createUI() splitter = new QSplitter(Qt::Vertical, this); splitter->setContentsMargins(0, 0, 0, 0); splitter->setHandleWidth(3); - splitter->setVisible(true); splitter->addWidget(tableWidget); splitter->addWidget(textComment); splitter->setSizes(QList({500, 140})); + /// Server Scripts + serverProjectCombo = new QComboBox(this); + serverProjectCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + serverTableWidget = createScriptTable(this, 3); + serverTableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("Name")); + serverTableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("Status")); + serverTableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("Description")); + serverTableWidget->hideColumn(2); + + serverTextComment = new QTextEdit(this); + serverTextComment->setReadOnly(true); + + serverSplitter = new QSplitter(Qt::Vertical, this); + serverSplitter->setContentsMargins(0, 0, 0, 0); + serverSplitter->setHandleWidth(3); + serverSplitter->addWidget(serverTableWidget); + serverSplitter->addWidget(serverTextComment); + serverSplitter->setSizes(QList({500, 140})); + + auto* serverLayout = new QVBoxLayout(); + serverLayout->setContentsMargins(4, 4, 4, 4); + serverLayout->setSpacing(4); + serverLayout->addWidget(serverProjectCombo); + serverLayout->addWidget(serverSplitter, 1); + + serverTab = new QWidget(this); + serverTab->setLayout(serverLayout); + + tabWidget = new QTabWidget(this); + tabWidget->addTab(splitter, "Local Scripts"); + tabWidget->addTab(serverTab, "Server Scripts"); + spacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); spacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); @@ -67,8 +108,8 @@ void DialogExtender::createUI() buttonClose->setFixedWidth(180); layout = new QGridLayout(this); - layout->setContentsMargins( 4, 4, 4, 4); - layout->addWidget(splitter, 0, 0, 1, 3); + layout->setContentsMargins(4, 4, 4, 4); + layout->addWidget(tabWidget, 0, 0, 1, 3); layout->addItem( spacer1, 1, 0, 1, 1); layout->addWidget(buttonClose, 1, 1, 1, 1); layout->addItem( spacer2, 1, 2, 1, 1); @@ -242,3 +283,136 @@ void DialogExtender::onActionRemove() const } void DialogExtender::onRowSelect(const int row, int column) const { textComment->setText(tableWidget->item(row,3)->text()); } + +/// Server Scripts + +void DialogExtender::SetMainUI(MainUI* ui) +{ + mainUI = ui; + RefreshProjectsList(); +} + +void DialogExtender::RefreshProjectsList() +{ + serverProjectCombo->blockSignals(true); + serverProjectCombo->clear(); + + if (!mainUI) { + serverProjectCombo->blockSignals(false); + return; + } + + auto projects = mainUI->GetAdaptixProjects(); + for (auto* widget : projects) { + if (widget && widget->GetProfile()) { + QString projectName = widget->GetProfile()->GetProject(); + serverProjectCombo->addItem(projectName, QVariant::fromValue(static_cast(widget))); + } + } + + serverProjectCombo->blockSignals(false); + + if (serverProjectCombo->count() > 0) { + serverProjectCombo->setCurrentIndex(0); + onProjectChanged(0); + } else { + currentAdaptixWidget = nullptr; + RefreshServerScripts(); + } +} + +void DialogExtender::onProjectChanged(int index) +{ + if (index < 0 || !serverProjectCombo) { + currentAdaptixWidget = nullptr; + } else { + currentAdaptixWidget = static_cast(serverProjectCombo->itemData(index).value()); + } + RefreshServerScripts(); +} + +void DialogExtender::RefreshServerScripts() +{ + serverTableWidget->setRowCount(0); + serverTextComment->clear(); + + if (!currentAdaptixWidget) + return; + + QList scripts = currentAdaptixWidget->GetServerScripts(); + for (const ServerScriptInfo &entry : scripts) { + auto item_Name = new QTableWidgetItem(entry.name); + auto item_Status = new QTableWidgetItem(""); + auto item_Desc = new QTableWidgetItem(entry.description); + + item_Name->setFlags(item_Name->flags() ^ Qt::ItemIsEditable); + + item_Status->setFlags(item_Status->flags() ^ Qt::ItemIsEditable); + item_Status->setTextAlignment(Qt::AlignCenter); + if (entry.enabled) { + item_Status->setText("Enable"); + item_Status->setForeground(QColor(COLOR_NeonGreen)); + } else { + item_Status->setText("Disable"); + item_Status->setForeground(QColor(COLOR_BrightOrange)); + } + + int row = serverTableWidget->rowCount(); + serverTableWidget->setRowCount(row + 1); + + bool isSortingEnabled = serverTableWidget->isSortingEnabled(); + serverTableWidget->setSortingEnabled(false); + serverTableWidget->setItem(row, 0, item_Name); + serverTableWidget->setItem(row, 1, item_Status); + serverTableWidget->setItem(row, 2, item_Desc); + serverTableWidget->setSortingEnabled(isSortingEnabled); + } + + serverTableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + serverTableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); +} + +void DialogExtender::handleServerMenu(const QPoint &pos) +{ + QMenu menu; + menu.addAction("Enable", this, &DialogExtender::onServerActionEnable); + menu.addAction("Disable", this, &DialogExtender::onServerActionDisable); + + QPoint globalPos = serverTableWidget->mapToGlobal(pos); + menu.exec(globalPos); +} + +void DialogExtender::onServerActionEnable() +{ + if (!currentAdaptixWidget) + return; + + for (int row = 0; row < serverTableWidget->rowCount(); ++row) { + if (serverTableWidget->item(row, 0)->isSelected()) { + QString name = serverTableWidget->item(row, 0)->text(); + currentAdaptixWidget->EnableServerScript(name); + } + } + RefreshServerScripts(); +} + +void DialogExtender::onServerActionDisable() +{ + if (!currentAdaptixWidget) + return; + + for (int row = 0; row < serverTableWidget->rowCount(); ++row) { + if (serverTableWidget->item(row, 0)->isSelected()) { + QString name = serverTableWidget->item(row, 0)->text(); + currentAdaptixWidget->DisableServerScript(name); + } + } + RefreshServerScripts(); +} + +void DialogExtender::onServerRowSelect(const int row, int column) const +{ + QTableWidgetItem* item = serverTableWidget->item(row, 2); + if (item) + serverTextComment->setText(item->text()); +} diff --git a/AdaptixClient/Source/UI/MainUI.cpp b/AdaptixClient/Source/UI/MainUI.cpp index 58a943c4..9dc0dc47 100644 --- a/AdaptixClient/Source/UI/MainUI.cpp +++ b/AdaptixClient/Source/UI/MainUI.cpp @@ -178,6 +178,11 @@ AuthProfile* MainUI::GetCurrentProfile() const return adaptixWidget->GetProfile(); } +QVector MainUI::GetAdaptixProjects() const +{ + return AdaptixProjects; +} + /// Actions void MainUI::onNewProject() { GlobalClient->NewProject(); } @@ -211,7 +216,11 @@ void MainUI::onAxScriptConsole() adaptixWidget->LoadAxConsoleUI(); } -void MainUI::onScriptManager() { GlobalClient->extender->dialogExtender->show(); } +void MainUI::onScriptManager() +{ + GlobalClient->extender->dialogExtender->SetMainUI(this); + GlobalClient->extender->dialogExtender->show(); +} void MainUI::onSettings() { GlobalClient->settings->getDialogSettings()->show(); } diff --git a/AdaptixClient/Source/UI/Widgets/AdaptixWidget.cpp b/AdaptixClient/Source/UI/Widgets/AdaptixWidget.cpp index e7c58a23..4d36fe44 100644 --- a/AdaptixClient/Source/UI/Widgets/AdaptixWidget.cpp +++ b/AdaptixClient/Source/UI/Widgets/AdaptixWidget.cpp @@ -575,9 +575,9 @@ void AdaptixWidget::ClearAdaptix() delete regAgent.commander; RegisterAgents.clear(); - for (auto regAgent : ServerRegAgents) - delete regAgent.commander; - ServerRegAgents.clear(); + RegisterListeners.clear(); + AgentTypes.clear(); + Listeners.clear(); } void AdaptixWidget::ClearChatStream() @@ -618,116 +618,228 @@ void AdaptixWidget::RegisterServiceConfig(const QString &serviceName, const QStr ScriptManager->ServiceScriptAdd(serviceName, ax_script); } -void AdaptixWidget::RegisterAgentConfig(const QString &agentName, const QString &ax_script, const QStringList &listeners, const bool &multiListeners) +static Argument parseArgument(const QJsonObject &argObj) +{ + Argument arg; + arg.type = argObj["type"].toString(); + arg.name = argObj["name"].toString(); + arg.required = argObj["required"].toBool(); + arg.flag = argObj["flag"].toBool(); + arg.mark = argObj["mark"].toString(); + arg.description = argObj["description"].toString(); + arg.defaultUsed = argObj["default_used"].toBool(); + if (arg.defaultUsed) + arg.defaultValue = argObj["default_value"].toVariant(); + return arg; +} + +static Command parseCommand(const QJsonObject &cmdObj) +{ + Command cmd; + cmd.name = cmdObj["name"].toString(); + cmd.message = cmdObj["message"].toString(); + cmd.description = cmdObj["description"].toString(); + cmd.example = cmdObj["example"].toString(); + cmd.is_pre_hook = cmdObj["has_pre_hook"].toBool(); + + for (const QJsonValue &argVal : cmdObj["args"].toArray()) { + if (argVal.isObject()) + cmd.args.append(parseArgument(argVal.toObject())); + } + + for (const QJsonValue &subVal : cmdObj["subcommands"].toArray()) { + if (subVal.isObject()) + cmd.subcommands.append(parseCommand(subVal.toObject())); + } + return cmd; +} + +static CommandsGroup parseCommandsGroup(const QString &scriptName, const QJsonArray &cmdsArray) +{ + QList commands; + for (const QJsonValue &cmdVal : cmdsArray) { + if (cmdVal.isObject()) + commands.append(parseCommand(cmdVal.toObject())); + } + + CommandsGroup cg; + cg.groupName = scriptName; + cg.commands = commands; + cg.engine = nullptr; + cg.filepath = QStringLiteral("__server__:") + scriptName; + return cg; +} + +void AdaptixWidget::RegisterAgentConfig(const QString &agentName, const QString &ax_script, const QStringList &listeners, const bool &multiListeners, const QJsonArray &groups) { AgentTypes[agentName] = AgentTypeInfo{multiListeners, listeners}; ScriptManager->AgentScriptAdd(agentName, ax_script); + for (const auto &listener : listeners) { + for (int os : {OS_WINDOWS, OS_LINUX, OS_MAC}) { + Commander* commander = new Commander(); + commander->SetAgentType(agentName); + + RegAgentConfig config = {agentName, listener, os, commander, true}; + RegisterAgents.push_back(config); + } + } + QJSEngine* engine = ScriptManager->AgentScriptEngine(agentName); - if (!engine) - return; + for (const QJsonValue &groupVal : groups) { + if (!groupVal.isObject()) + continue; -void AdaptixWidget::ProcessAxScriptCommands(const QString &agentName, const QString &listenerType, int os, const QString &commandsJson) -{ - /// Remove existing server commands for this agent/listener/os - for (int i = ServerRegAgents.size() - 1; i >= 0; --i) { - if (ServerRegAgents[i].name == agentName && ServerRegAgents[i].listenerType == listenerType && ServerRegAgents[i].os == os) { - delete ServerRegAgents[i].commander; - ServerRegAgents.removeAt(i); - } - } - - QJsonParseError parseError; - QJsonDocument doc = QJsonDocument::fromJson(commandsJson.toUtf8(), &parseError); - if (parseError.error != QJsonParseError::NoError || !doc.isArray()) - return; - - QJsonArray groupsArray = doc.array(); - QList allCommands; - - for (const QJsonValue &groupVal : groupsArray) { - if (!groupVal.isObject()) continue; QJsonObject groupObj = groupVal.toObject(); - QJsonArray cmdsArray = groupObj["commands"].toArray(); + QString gAgent = groupObj["agent"].toString(); + QString gListener = groupObj["listener"].toString(); + int gOs = static_cast(groupObj["os"].toDouble()); + QString commandsJson = groupObj["commands"].toString(); - for (const QJsonValue &cmdVal : cmdsArray) { - if (!cmdVal.isObject()) continue; - QJsonObject cmdObj = cmdVal.toObject(); + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(commandsJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isArray()) + continue; - Command cmd; - cmd.name = cmdObj["name"].toString(); - cmd.message = cmdObj["message"].toString(); - cmd.description = cmdObj["description"].toString(); - cmd.example = cmdObj["example"].toString(); - cmd.is_pre_hook = cmdObj["has_pre_hook"].toBool(); + QJsonArray commandGroupsArray = doc.array(); + for (const QJsonValue &cgVal : commandGroupsArray) { + if (!cgVal.isObject()) + continue; - QJsonArray argsArray = cmdObj["args"].toArray(); - for (const QJsonValue &argVal : argsArray) { - if (!argVal.isObject()) continue; - QJsonObject argObj = argVal.toObject(); - Argument arg; - arg.type = argObj["type"].toString(); - arg.name = argObj["name"].toString(); - arg.required = argObj["required"].toBool(); - arg.flag = argObj["flag"].toBool(); - arg.mark = argObj["mark"].toString(); - arg.description = argObj["description"].toString(); - arg.defaultUsed = argObj["default_used"].toBool(); - if (arg.defaultUsed) - arg.defaultValue = argObj["default_value"].toVariant(); - cmd.args.append(arg); - } + QJsonObject cgObj = cgVal.toObject(); + QString groupName = cgObj["groupName"].toString(); + QString groupDesc = cgObj["groupDescription"].toString(); + QJsonArray cmdsArray = cgObj["commands"].toArray(); - QJsonArray subsArray = cmdObj["subcommands"].toArray(); - for (const QJsonValue &subVal : subsArray) { - if (!subVal.isObject()) continue; - QJsonObject subObj = subVal.toObject(); - Command sub; - sub.name = subObj["name"].toString(); - sub.message = subObj["message"].toString(); - sub.description = subObj["description"].toString(); - sub.example = subObj["example"].toString(); - sub.is_pre_hook = subObj["has_pre_hook"].toBool(); + if (groupName.isEmpty()) + groupName = agentName; - QJsonArray subArgsArray = subObj["args"].toArray(); - for (const QJsonValue &saVal : subArgsArray) { - if (!saVal.isObject()) continue; - QJsonObject saObj = saVal.toObject(); - Argument sa; - sa.type = saObj["type"].toString(); - sa.name = saObj["name"].toString(); - sa.required = saObj["required"].toBool(); - sa.flag = saObj["flag"].toBool(); - sa.mark = saObj["mark"].toString(); - sa.description = saObj["description"].toString(); - sa.defaultUsed = saObj["default_used"].toBool(); - if (sa.defaultUsed) - sa.defaultValue = saObj["default_value"].toVariant(); - sub.args.append(sa); + CommandsGroup cg = parseCommandsGroup(groupName, cmdsArray); + if (cg.commands.isEmpty()) + continue; + + cg.engine = engine; + + for (auto ®Agent : this->RegisterAgents) { + if (regAgent.name != gAgent || regAgent.os != gOs) + continue; + bool listenerMatch = gListener.isEmpty() || regAgent.listenerType.isEmpty() || regAgent.listenerType == gListener; + if (listenerMatch) { + regAgent.commander->SetMainCommands(cg); } - cmd.subcommands.append(sub); + } + } + } +} + +void AdaptixWidget::registerServerCommandGroups(const QString &scriptName, const QList &groups, QJSEngine* engine) +{ + for (const auto &group : groups) { + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(group.commandsJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isArray()) + continue; + + for (const QJsonValue &groupVal : doc.array()) { + if (!groupVal.isObject()) + continue; + + QJsonObject groupObj = groupVal.toObject(); + QString groupName = groupObj["groupName"].toString(); + QString groupDesc = groupObj["groupDescription"].toString(); + + if (groupName.isEmpty()) + groupName = scriptName; + + CommandsGroup cg = parseCommandsGroup(groupName, groupObj["commands"].toArray()); + if (cg.commands.isEmpty()) + continue; + + cg.engine = engine; + + for (auto ®Agent : this->RegisterAgents) { + if (regAgent.name != group.agentName || regAgent.os != group.os) + continue; + bool listenerMatch = group.listenerType.isEmpty() || regAgent.listenerType.isEmpty() || regAgent.listenerType == group.listenerType; + if (listenerMatch) + regAgent.commander->AddServerGroup(groupName, groupDesc, cg); } - allCommands.append(cmd); + QReadLocker locker(&AgentsMapLock); + for (auto agent : AgentsMap) { + if (agent->data.Name != group.agentName || agent->data.Os != group.os) + continue; + bool lMatch = group.listenerType.isEmpty() || agent->listenerType.isEmpty() || agent->listenerType == group.listenerType; + if (lMatch) + agent->commander->AddServerGroup(groupName, groupDesc, cg); + } } } - if (allCommands.isEmpty()) + if (engine) { + for (auto ®Agent : this->RegisterAgents) + regAgent.commander->SetServerGroupEngine(scriptName, engine); + + QReadLocker locker(&AgentsMapLock); + for (auto agent : AgentsMap) + agent->commander->SetServerGroupEngine(scriptName, engine); + } +} + +void AdaptixWidget::ProcessAxScriptPacket(const QString &name, const QString &content, const QJsonArray &groups) +{ + ServerScriptData scriptData; + scriptData.name = name; + scriptData.code = content; + scriptData.enabled = true; + + for (const QJsonValue &groupVal : groups) { + if (!groupVal.isObject()) + continue; + + QJsonObject groupObj = groupVal.toObject(); + ServerScriptGroup sg; + sg.agentName = groupObj["agent"].toString(); + sg.listenerType = groupObj["listener"].toString(); + sg.os = static_cast(groupObj["os"].toDouble()); + sg.commandsJson = groupObj["commands"].toString(); + scriptData.groups.append(sg); + } + + ScriptManager->ServerScriptAdd(scriptData); + registerServerCommandGroups(name, scriptData.groups, ScriptManager->ServerScriptEngine(name)); +} + +void AdaptixWidget::EnableServerScript(const QString &name) +{ + ServerScriptData data = ScriptManager->ServerScriptGet(name); + if (data.name.isEmpty()) return; - CommandsGroup group; - group.groupName = agentName; - group.commands = allCommands; - group.engine = nullptr; - group.filepath = ""; + ScriptManager->ServerScriptSetEnabled(name, true); + registerServerCommandGroups(name, data.groups, ScriptManager->ServerScriptEngine(name)); +} - Commander* commander = new Commander(); - commander->AddRegCommands(group); +void AdaptixWidget::DisableServerScript(const QString &name) +{ + ScriptManager->ServerScriptSetEnabled(name, false); - RegAgentConfig config = {agentName, listenerType, os, commander, true}; - ServerRegAgents.push_back(config); + for (auto ®Agent : this->RegisterAgents) + regAgent.commander->RemoveServerGroup(name); + + QReadLocker locker(&AgentsMapLock); + for (auto agent : AgentsMap) + agent->commander->RemoveServerGroup(name); +} + +QList AdaptixWidget::GetServerScripts() const +{ + QList result; + for (const auto &data : ScriptManager->ServerScriptList()) + result.append({data.name, data.description, data.enabled}); + return result; } RegListenerConfig AdaptixWidget::GetRegListener(const QString &listenerName) @@ -764,16 +876,12 @@ RegAgentConfig AdaptixWidget::GetRegAgent(const QString &agentName, const QStrin break; } } - for (auto regAgent : this->ServerRegAgents) { + for (auto regAgent : this->RegisterAgents) { if (regAgent.name == agentName && regAgent.listenerType == listener && regAgent.os == os) return regAgent; } - for (auto regAgent : this->ServerRegAgents) { - if (regAgent.name == agentName && regAgent.listenerType.isEmpty() && regAgent.os == os) - return regAgent; - } for (auto regAgent : this->RegisterAgents) { - if (regAgent.name == agentName && regAgent.listenerType == listener && regAgent.os == os) + if (regAgent.name == agentName && regAgent.listenerType.isEmpty() && regAgent.os == os) return regAgent; } for (auto regAgent : this->RegisterAgents) { @@ -788,12 +896,6 @@ QList AdaptixWidget::GetCommanders(const QStringList &listeners, con { QList commanders; for (auto regAgent : this->RegisterAgents) { - if ( !agents.contains(regAgent.name) ) continue; - if ( !listeners.empty() && !listeners.contains(regAgent.listenerType)) continue; - if ( !os.empty() && !os.contains(regAgent.os) ) continue; - commanders.append(regAgent.commander); - } - for (auto regAgent : this->ServerRegAgents) { if ( !agents.contains(regAgent.name) ) continue; if ( !listeners.empty() && !regAgent.listenerType.isEmpty() && !listeners.contains(regAgent.listenerType)) continue; if ( !os.empty() && !os.contains(regAgent.os) ) continue; @@ -807,11 +909,42 @@ QList AdaptixWidget::GetCommandersAll() const QList commanders; for (auto regAgent : this->RegisterAgents) commanders.append(regAgent.commander); - for (auto regAgent : this->ServerRegAgents) - commanders.append(regAgent.commander); return commanders; } +void AdaptixWidget::AddCommandsToCommanders(const CommandsGroup &group, const QStringList &listeners, const QStringList &agents, const QList &osList) +{ + QList effectiveOs = osList.isEmpty() ? QList{OS_WINDOWS, OS_LINUX, OS_MAC} : osList; + QStringList effectiveListeners = listeners.isEmpty() ? QStringList{""} : listeners; + + for (const QString &agentName : agents) { + for (int os : effectiveOs) { + for (const QString &listener : effectiveListeners) { + Commander* targetCommander = nullptr; + + for (auto ®Agent : this->RegisterAgents) { + if (regAgent.name == agentName && regAgent.os == os) { + bool listenerMatch = listener.isEmpty() || regAgent.listenerType.isEmpty() || regAgent.listenerType == listener; + if (listenerMatch) { + targetCommander = regAgent.commander; + break; + } + } + } + + if (!targetCommander) { + targetCommander = new Commander(); + targetCommander->SetAgentType(agentName); + RegAgentConfig config = {agentName, listener, os, targetCommander, true}; + RegisterAgents.push_back(config); + } + + targetCommander->AddClientGroup(group); + } + } + } +} + void AdaptixWidget::PostHookProcess(QJsonObject jsonHookObj) { QString hookId = jsonHookObj["a_hook_id"].toString(); diff --git a/AdaptixClient/Source/Workers/WebSocketWorker.cpp b/AdaptixClient/Source/Workers/WebSocketWorker.cpp index 7f34268f..08f3e2e7 100644 --- a/AdaptixClient/Source/Workers/WebSocketWorker.cpp +++ b/AdaptixClient/Source/Workers/WebSocketWorker.cpp @@ -32,6 +32,7 @@ void WebSocketWorker::run() pingTimer = nullptr; } if (webSocket) { + webSocket->blockSignals(true); webSocket->abort(); delete webSocket; webSocket = nullptr; @@ -191,7 +192,7 @@ void WebSocketWorker::stopWorker() pingTimer = nullptr; } if (webSocket) { - disconnect(webSocket, nullptr, nullptr, nullptr); + webSocket->blockSignals(true); webSocket->abort(); delete webSocket; webSocket = nullptr; diff --git a/AdaptixServer/core/axscript/bridge_ax.go b/AdaptixServer/core/axscript/bridge_ax.go index 321e524e..e095f157 100644 --- a/AdaptixServer/core/axscript/bridge_ax.go +++ b/AdaptixServer/core/axscript/bridge_ax.go @@ -132,8 +132,12 @@ func registerAxBridge(engine *ScriptEngine) { } agentId := call.Argument(0).String() + displayCmdline := call.Argument(1).String() aliasCmdline := call.Argument(2).String() - // arg3 = message (optional, used by client but not needed for server dispatch) + message := "" + if len(call.Arguments) > 3 && !goja.IsUndefined(call.Argument(3)) && !goja.IsNull(call.Argument(3)) { + message = call.Argument(3).String() + } // arg4 = hook (optional) // arg5 = handler (optional) @@ -153,7 +157,7 @@ func registerAxBridge(engine *ScriptEngine) { } } - err := engine.manager.ExecuteAliasWithHooks(engine, agentId, aliasCmdline, postHookFn, handlerFn) + err := engine.manager.ExecuteAliasWithHooks(engine, agentId, displayCmdline, aliasCmdline, message, postHookFn, handlerFn) if err != nil { panic(rt.NewGoError(err)) } @@ -169,12 +173,17 @@ func registerAxBridge(engine *ScriptEngine) { panic(rt.NewTypeError("execute_alias_hook requires 5 arguments")) } agentId := call.Argument(0).String() + displayCmdline := call.Argument(1).String() aliasCmdline := call.Argument(2).String() + message := "" + if len(call.Arguments) > 3 && !goja.IsUndefined(call.Argument(3)) && !goja.IsNull(call.Argument(3)) { + message = call.Argument(3).String() + } var hookFn goja.Callable if fn, ok := goja.AssertFunction(call.Argument(4)); ok { hookFn = fn } - err := engine.manager.ExecuteAliasWithHooks(engine, agentId, aliasCmdline, hookFn, nil) + err := engine.manager.ExecuteAliasWithHooks(engine, agentId, displayCmdline, aliasCmdline, message, hookFn, nil) if err != nil { panic(rt.NewGoError(err)) } @@ -189,12 +198,17 @@ func registerAxBridge(engine *ScriptEngine) { panic(rt.NewTypeError("execute_alias_handler requires 5 arguments")) } agentId := call.Argument(0).String() + displayCmdline := call.Argument(1).String() aliasCmdline := call.Argument(2).String() + message := "" + if len(call.Arguments) > 3 && !goja.IsUndefined(call.Argument(3)) && !goja.IsNull(call.Argument(3)) { + message = call.Argument(3).String() + } var handlerFn goja.Callable if fn, ok := goja.AssertFunction(call.Argument(4)); ok { handlerFn = fn } - err := engine.manager.ExecuteAliasWithHooks(engine, agentId, aliasCmdline, nil, handlerFn) + err := engine.manager.ExecuteAliasWithHooks(engine, agentId, displayCmdline, aliasCmdline, message, nil, handlerFn) if err != nil { panic(rt.NewGoError(err)) } @@ -260,14 +274,14 @@ func registerAxBridge(engine *ScriptEngine) { axObj.Set("log", func(call goja.FunctionCall) goja.Value { if len(call.Arguments) > 0 { - logs.Info("AxScript", "[%s] %s", engine.name, call.Argument(0).String()) + logs.Info("", "[%s] %s", engine.name, call.Argument(0).String()) } return goja.Undefined() }) axObj.Set("log_error", func(call goja.FunctionCall) goja.Value { if len(call.Arguments) > 0 { - logs.Error("AxScript", "[%s] %s", engine.name, call.Argument(0).String()) + logs.Error("", "[%s] %s", engine.name, call.Argument(0).String()) } return goja.Undefined() }) @@ -339,10 +353,27 @@ func registerAxBridge(engine *ScriptEngine) { } if groupBuilder == nil { - logs.Warn("AxScript", "register_commands_group: invalid group object") + logs.Warn("", "register_commands_group: invalid group object") return goja.Undefined() } + metaVal := rt.Get("metadata") + if metaVal != nil && !goja.IsUndefined(metaVal) && !goja.IsNull(metaVal) { + metaObj := metaVal.ToObject(rt) + if groupBuilder.name == "" { + nameVal := metaObj.Get("name") + if nameVal != nil && !goja.IsUndefined(nameVal) && !goja.IsNull(nameVal) { + groupBuilder.name = nameVal.String() + } + } + if groupBuilder.description == "" { + descVal := metaObj.Get("description") + if descVal != nil && !goja.IsUndefined(descVal) && !goja.IsNull(descVal) { + groupBuilder.description = descVal.String() + } + } + } + agentNames := exportStringArray(rt, agentsVal) var osList []int @@ -366,12 +397,23 @@ func registerAxBridge(engine *ScriptEngine) { listenerTypes = []string{""} } + var sourceType SourceType + if strings.HasPrefix(engine.name, "user:") { + sourceType = SourceUser + } else { + sourceType = SourceProfile + } + + scriptName := engine.GetMetadataName() + if scriptName == "" { + scriptName = filepath.Base(engine.scriptPath) + } + for _, agentName := range agentNames { - group := groupBuilder.ToCommandGroup(agentName) + group := groupBuilder.ToCommandGroup(scriptName) for _, listener := range listenerTypes { for _, osType := range osList { - engine.manager.Registry.RegisterGroups(agentName, listener, osType, []CommandGroup{group}, engine) - logs.Debug("AxScript", "Registered %d commands for '%s' listener='%s' os=%s via register_commands_group", len(group.Commands), agentName, listener, OsToString(osType)) + engine.manager.CommandStore.RegisterGroups(sourceType, agentName, listener, osType, []CommandGroup{group}, engine) } } } @@ -396,7 +438,7 @@ func registerAxBridge(engine *ScriptEngine) { path := call.Argument(0).String() err := engine.manager.LoadAxScriptChild(engine, path) if err != nil { - logs.Error("AxScript", "script_load error: %v", err) + logs.Error("", "script_load error: %v", err) panic(rt.NewGoError(err)) } return goja.Undefined() @@ -407,11 +449,12 @@ func registerAxBridge(engine *ScriptEngine) { return goja.Undefined() } path := call.Argument(0).String() - err := engine.manager.ImportAxScript(engine, path) + absPath, err := engine.manager.ImportAxScript(engine, path) if err != nil { - logs.Error("AxScript", "script_import error: %v", err) + logs.Error("", "script_import error: %v", err) panic(rt.NewGoError(err)) } + engine.AddImportedFile(absPath) return goja.Undefined() }) @@ -422,7 +465,7 @@ func registerAxBridge(engine *ScriptEngine) { name := call.Argument(0).String() err := engine.manager.UnloadAxScript(name) if err != nil { - logs.Warn("AxScript", "script_unload error: %v", err) + logs.Warn("", "script_unload error: %v", err) } return goja.Undefined() }) @@ -745,12 +788,12 @@ func registerAxBridge(engine *ScriptEngine) { }) axObj.Set("file_write", func(call goja.FunctionCall) goja.Value { - logs.Warn("AxScript", "file_write is disabled on server") + logs.Warn("", "file_write is disabled on server") return rt.ToValue(false) }) axObj.Set("file_write_text", func(call goja.FunctionCall) goja.Value { - logs.Warn("AxScript", "file_write_text is disabled on server") + logs.Warn("", "file_write_text is disabled on server") return rt.ToValue(false) // if engine.manager == nil || len(call.Arguments) < 2 { @@ -764,14 +807,14 @@ func registerAxBridge(engine *ScriptEngine) { // } // err := engine.manager.WriteFileSandboxed(engine, path, []byte(content), appendMode) // if err != nil { - // logs.Warn("AxScript", "file_write_text error: %v", err) + // logs.Warn("", "file_write_text error: %v", err) // return rt.ToValue(false) // } // return rt.ToValue(true) }) axObj.Set("file_write_binary", func(call goja.FunctionCall) goja.Value { - logs.Warn("AxScript", "file_write_binary is disabled on server") + logs.Warn("", "file_write_binary is disabled on server") return rt.ToValue(false) // if engine.manager == nil || len(call.Arguments) < 2 { @@ -781,12 +824,12 @@ func registerAxBridge(engine *ScriptEngine) { // b64Content := call.Argument(1).String() // data, err := base64.StdEncoding.DecodeString(b64Content) // if err != nil { - // logs.Warn("AxScript", "file_write_binary: invalid base64: %v", err) + // logs.Warn("", "file_write_binary: invalid base64: %v", err) // return rt.ToValue(false) // } // err = engine.manager.WriteFileSandboxed(engine, path, data, false) // if err != nil { - // logs.Warn("AxScript", "file_write_binary error: %v", err) + // logs.Warn("", "file_write_binary error: %v", err) // return rt.ToValue(false) // } // return rt.ToValue(true) @@ -917,7 +960,7 @@ func registerAxBridge(engine *ScriptEngine) { axObj.Set("show_message", func(call goja.FunctionCall) goja.Value { if len(call.Arguments) >= 2 { - logs.Info("AxScript", "[%s] Message: %s - %s", engine.name, call.Argument(0).String(), call.Argument(1).String()) + logs.Info("", "[%s] Message: %s - %s", engine.name, call.Argument(0).String(), call.Argument(1).String()) } return goja.Undefined() }) @@ -971,8 +1014,9 @@ func exportStringArray(rt *goja.Runtime, val goja.Value) []string { return nil } +// /--- func convertQtFormatToGo(qtFmt string) string { - // Qt-to-Go date format conversion for common patterns + // Simple Qt-to-Go date format conversion for common patterns r := strings.NewReplacer( "yyyy", "2006", "yy", "06", "MM", "01", "M", "1", @@ -1255,6 +1299,7 @@ func registerAxUtilities(axObj *goja.Object, rt *goja.Runtime, engine *ScriptEng }) } +// /--- func formatBytes(b uint64) string { const unit = 1024 if b < unit { diff --git a/AdaptixServer/core/axscript/bridge_command.go b/AdaptixServer/core/axscript/bridge_command.go index def692cf..7e81a2fd 100644 --- a/AdaptixServer/core/axscript/bridge_command.go +++ b/AdaptixServer/core/axscript/bridge_command.go @@ -6,6 +6,8 @@ import ( "github.com/dop251/goja" ) +/// Command + type jsCommandBuilder struct { engine *ScriptEngine command CommandDef @@ -242,18 +244,23 @@ func (b *jsCommandBuilder) SetPreHook(call goja.FunctionCall) goja.Value { return goja.Undefined() } +/// CommandGroup + type jsCommandGroupBuilder struct { - engine *ScriptEngine - name string - commands []CommandDef + engine *ScriptEngine + name string + description string + commands []CommandDef } +// /--- func newJsCommandGroupBuilder(engine *ScriptEngine) *jsCommandGroupBuilder { return &jsCommandGroupBuilder{ engine: engine, } } +// /--- func (g *jsCommandGroupBuilder) SetParams(call goja.FunctionCall) goja.Value { g.name = call.Argument(0).String() @@ -299,6 +306,7 @@ func (g *jsCommandGroupBuilder) SetParamsFromValue(name string, val goja.Value) } } +// /--- func (g *jsCommandGroupBuilder) Add(call goja.FunctionCall) goja.Value { val := call.Argument(0) if goja.IsUndefined(val) || goja.IsNull(val) { @@ -326,9 +334,10 @@ func (g *jsCommandGroupBuilder) Add(call goja.FunctionCall) goja.Value { func (g *jsCommandGroupBuilder) ToCommandGroup(scriptName string) CommandGroup { return CommandGroup{ - GroupName: g.name, - ScriptName: scriptName, - Commands: g.commands, + GroupName: g.name, + GroupDescription: g.description, + ScriptName: scriptName, + Commands: g.commands, } } diff --git a/AdaptixServer/core/axscript/bridge_helpers.go b/AdaptixServer/core/axscript/bridge_helpers.go index 619f88fc..31377e10 100644 --- a/AdaptixServer/core/axscript/bridge_helpers.go +++ b/AdaptixServer/core/axscript/bridge_helpers.go @@ -7,6 +7,7 @@ import ( "strings" ) +// /--- func bytesToCode(language string, data []byte, varName string) string { if len(data) == 0 { return "" @@ -94,6 +95,7 @@ func bytesToCode(language string, data []byte, varName string) string { } } +// /--- func encodeData(alg string, data []byte, key string) string { switch alg { case "base64": @@ -115,6 +117,7 @@ func encodeData(alg string, data []byte, key string) string { } } +// /--- func decodeData(alg string, data string, key string) string { switch alg { case "base64": @@ -148,6 +151,7 @@ func decodeData(alg string, data string, key string) string { } } +// /--- func decodeRawData(alg string, rawData []byte, key string) []byte { switch alg { case "xor": diff --git a/AdaptixServer/core/axscript/bridge_stubs.go b/AdaptixServer/core/axscript/bridge_stubs.go index 34d977b4..4bd15e0a 100644 --- a/AdaptixServer/core/axscript/bridge_stubs.go +++ b/AdaptixServer/core/axscript/bridge_stubs.go @@ -6,7 +6,6 @@ import ( "github.com/dop251/goja" ) -// newStubWidget creates a generic no-op widget object with common methods. func newStubWidget(rt *goja.Runtime) *goja.Object { obj := rt.NewObject() obj.Set("setEnabled", func(goja.FunctionCall) goja.Value { return goja.Undefined() }) @@ -189,7 +188,6 @@ func registerEventStubs(engine *ScriptEngine) { rt.Set("event", eventObj) } -// fileBasename returns the last component of a path (used by ax.file_basename). func fileBasename(path string) string { return filepath.Base(path) } diff --git a/AdaptixServer/core/axscript/engine.go b/AdaptixServer/core/axscript/engine.go index c59385c2..268323e5 100644 --- a/AdaptixServer/core/axscript/engine.go +++ b/AdaptixServer/core/axscript/engine.go @@ -1,8 +1,8 @@ package axscript import ( + "AdaptixServer/core/utils/fsystem" "fmt" - "os" "path/filepath" "strings" "sync" @@ -15,11 +15,12 @@ type ScriptEngine struct { runtime *goja.Runtime name string - manager *ScriptManager - functions map[string]goja.Callable - scriptPath string // absolute path to the .axs file - scriptDir string // directory containing the .axs file (with trailing /) - allowedRoots []string // resolved absolute paths allowed for file access + manager *ScriptManager + functions map[string]goja.Callable + scriptPath string + scriptDir string + allowedRoots []string + importedFiles []string } func NewScriptEngine(name string, manager *ScriptManager) *ScriptEngine { @@ -50,7 +51,7 @@ func NewScriptEngineFromPath(scriptPath string, manager *ScriptManager) (*Script return nil, fmt.Errorf("invalid script path: %w", err) } - resolved, err := resolveRealPath(abs) + resolved, err := fsystem.ResolveRealPath(abs) if err != nil { return nil, fmt.Errorf("cannot resolve script path: %w", err) } @@ -71,10 +72,25 @@ func NewScriptEngineFromPath(scriptPath string, manager *ScriptManager) (*Script return engine, nil } +func (e *ScriptEngine) Execute(script string) error { + e.mu.Lock() + defer e.mu.Unlock() + + _, err := e.runtime.RunString(script) + if err != nil { + return fmt.Errorf("script execution error in '%s': %w", e.name, err) + } + return nil +} + +////////// + +// /--- func (e *ScriptEngine) ScriptDir() string { return e.scriptDir } +// /--- func (e *ScriptEngine) ScriptPath() string { return e.scriptPath } @@ -85,7 +101,7 @@ func (e *ScriptEngine) ValidatePath(path string) (string, error) { return "", fmt.Errorf("invalid path: %w", err) } - real, err := resolveRealPath(abs) + real, err := fsystem.ResolveRealPath(abs) if err != nil { return "", fmt.Errorf("cannot resolve path: %w", err) } @@ -99,42 +115,32 @@ func (e *ScriptEngine) ValidatePath(path string) (string, error) { return "", fmt.Errorf("access denied: %s is outside allowed directories", path) } -func resolveRealPath(path string) (string, error) { - info, err := os.Lstat(path) - if err != nil { - return filepath.Clean(path), nil - } - - if info.Mode()&os.ModeSymlink != 0 { - resolved, err := filepath.EvalSymlinks(path) - if err != nil { - return "", err +func (e *ScriptEngine) AddImportedFile(path string) { + for _, p := range e.importedFiles { + if p == path { + return } - return filepath.Clean(resolved), nil } - - return filepath.Clean(path), nil + e.importedFiles = append(e.importedFiles, path) } +func (e *ScriptEngine) GetImportedFiles() []string { + result := make([]string, len(e.importedFiles)) + copy(result, e.importedFiles) + return result +} + +// /--- func (e *ScriptEngine) Name() string { return e.name } +// /--- func (e *ScriptEngine) Runtime() *goja.Runtime { return e.runtime } -func (e *ScriptEngine) Execute(script string) error { - e.mu.Lock() - defer e.mu.Unlock() - - _, err := e.runtime.RunString(script) - if err != nil { - return fmt.Errorf("script execution error in '%s': %w", e.name, err) - } - return nil -} - +// /--- func (e *ScriptEngine) CallFunction(name string, args ...goja.Value) (goja.Value, error) { e.mu.Lock() defer e.mu.Unlock() @@ -162,12 +168,14 @@ func (e *ScriptEngine) CallCallable(fn goja.Callable, args ...goja.Value) (goja. return result, nil } +// /--- func (e *ScriptEngine) StoreFunctionRef(id string, fn goja.Callable) { e.mu.Lock() defer e.mu.Unlock() e.functions[id] = fn } +// /--- func (e *ScriptEngine) GetFunctionRef(id string) (goja.Callable, bool) { e.mu.Lock() defer e.mu.Unlock() @@ -175,6 +183,7 @@ func (e *ScriptEngine) GetFunctionRef(id string) (goja.Callable, bool) { return fn, ok } +// /--- func (e *ScriptEngine) RemoveFunctionRef(id string) { e.mu.Lock() defer e.mu.Unlock() @@ -185,6 +194,39 @@ func (e *ScriptEngine) ToValue(v interface{}) goja.Value { return e.runtime.ToValue(v) } +// /--- func (e *ScriptEngine) SetGlobal(name string, value interface{}) { e.runtime.Set(name, value) } + +func (e *ScriptEngine) GetMetadataName() string { + metaVal := e.runtime.Get("metadata") + if metaVal == nil || goja.IsUndefined(metaVal) || goja.IsNull(metaVal) { + return "" + } + metaObj := metaVal.ToObject(e.runtime) + if metaObj == nil { + return "" + } + nameVal := metaObj.Get("name") + if nameVal == nil || goja.IsUndefined(nameVal) || goja.IsNull(nameVal) { + return "" + } + return nameVal.String() +} + +func (e *ScriptEngine) GetMetadataNoSave() bool { + metaVal := e.runtime.Get("metadata") + if metaVal == nil || goja.IsUndefined(metaVal) || goja.IsNull(metaVal) { + return false + } + metaObj := metaVal.ToObject(e.runtime) + if metaObj == nil { + return false + } + nosaveVal := metaObj.Get("nosave") + if nosaveVal == nil || goja.IsUndefined(nosaveVal) || goja.IsNull(nosaveVal) { + return false + } + return nosaveVal.ToBoolean() +} diff --git a/AdaptixServer/core/axscript/hooks.go b/AdaptixServer/core/axscript/hooks.go index cb182a0b..365ff4fe 100644 --- a/AdaptixServer/core/axscript/hooks.go +++ b/AdaptixServer/core/axscript/hooks.go @@ -64,6 +64,7 @@ func (hs *HookStore) GetPostHook(hookId string) *PendingHook { return hs.pendingPostHooks[hookId] } +// /--- func (hs *HookStore) GetHandler(handlerId string) *PendingHook { hs.mu.RLock() defer hs.mu.RUnlock() @@ -78,6 +79,7 @@ func (hs *HookStore) RemovePostHook(hookId string) { delete(hs.pendingPostHooks, hookId) } +// /--- func (hs *HookStore) RemoveHandler(handlerId string) { hs.mu.Lock() defer hs.mu.Unlock() @@ -107,6 +109,7 @@ func (hs *HookStore) ExecutePostHook(hookId string, data map[string]interface{}) return data, nil } +// /--- func (hs *HookStore) ExecuteHandler(handlerId string, data map[string]interface{}) error { hook := hs.GetHandler(handlerId) if hook == nil { diff --git a/AdaptixServer/core/axscript/manager.go b/AdaptixServer/core/axscript/manager.go index 2d87801d..e93a6074 100644 --- a/AdaptixServer/core/axscript/manager.go +++ b/AdaptixServer/core/axscript/manager.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "sync" "github.com/dop251/goja" @@ -17,8 +18,7 @@ type TeamserverBridge interface { TsAgentConsoleOutput(agentId string, messageType int, message string, clearText string, store bool) TsAgentConsoleErrorCommand(agentId string, client string, cmdline string, message string, HookId string, HandlerId string) - AxGetAgentNameById(agentId string) (string, int, error) - AxGetAgentListenerRegName(agentId string) (string, error) + AxGetAgentContext(agentId string) (agentName string, listenerRegName string, osType int, err error) AxGetAgents() map[string]interface{} AxGetAgentInfo(agentId string, property string) interface{} AxGetAgentIds() []string @@ -44,9 +44,9 @@ type TeamserverBridge interface { type ScriptManager struct { mu sync.RWMutex - teamserver TeamserverBridge - Registry *CommandRegistry - HookStore *HookStore + teamserver TeamserverBridge + CommandStore *CommandStore + HookStore *HookStore agentEngines map[string]*ScriptEngine userEngines map[string]*ScriptEngine @@ -58,7 +58,7 @@ type ScriptManager struct { func NewScriptManager(ts TeamserverBridge) *ScriptManager { return &ScriptManager{ teamserver: ts, - Registry: NewCommandRegistry(), + CommandStore: NewCommandStore(), HookStore: NewHookStore(), agentEngines: make(map[string]*ScriptEngine), userEngines: make(map[string]*ScriptEngine), @@ -66,6 +66,8 @@ func NewScriptManager(ts TeamserverBridge) *ScriptManager { } } +/// LOAD SCRIPTS + func (sm *ScriptManager) LoadAgentScript(agentName string, axScript string, listeners []string) error { engine := NewScriptEngine("agent:"+agentName, sm) @@ -91,15 +93,158 @@ func (sm *ScriptManager) LoadAgentScript(agentName string, axScript string, list for _, listenerType := range listeners { sm.executeRegisterCommands(engine, agentName, listenerType) } - if len(listeners) == 0 { sm.executeRegisterCommands(engine, agentName, "") } - - logs.Success("AxScript", "Loaded agent script for '%s'", agentName) return nil } +func (sm *ScriptManager) LoadAxScript(scriptPath string) error { + abs, err := filepath.Abs(scriptPath) + if err != nil { + return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) + } + + _, err = os.Stat(abs) + if err != nil { + return fmt.Errorf("script file not found: %s", abs) + } + + content, err := os.ReadFile(abs) + if err != nil { + return fmt.Errorf("failed to read script '%s': %w", abs, err) + } + + engine, err := NewScriptEngineFromPath(abs, sm) + if err != nil { + return fmt.Errorf("failed to create engine for '%s': %w", abs, err) + } + + registerFormStubs(engine) + registerMenuStubs(engine) + registerEventStubs(engine) + registerAxBridge(engine) + + err = engine.Execute(string(content)) + if err != nil { + return fmt.Errorf("failed to execute script '%s': %w", abs, err) + } + + if engine.GetMetadataNoSave() { + logs.Success("", "Executed axscript '%s' (nosave)", scriptPath) + return nil + } + + scriptName := engine.GetMetadataName() + if scriptName == "" { + scriptName = filepath.Base(abs) + } + + sm.mu.Lock() + sm.axscriptEngines[abs] = engine + sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ + Name: scriptName, + ScriptType: "axscript", + Path: abs, + }) + sm.mu.Unlock() + + logs.Success("", "Loaded axscript '%s'", scriptPath) + return nil +} + +func (sm *ScriptManager) LoadAxScriptChild(parentEngine *ScriptEngine, scriptPath string) error { + abs, err := filepath.Abs(scriptPath) + if err != nil { + return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) + } + + if _, err := os.Stat(abs); err != nil { + return fmt.Errorf("script file not found: %s", abs) + } + + content, err := os.ReadFile(abs) + if err != nil { + return fmt.Errorf("failed to read script '%s': %w", abs, err) + } + + engine, err := NewScriptEngineFromPath(abs, sm) + if err != nil { + return fmt.Errorf("failed to create engine for '%s': %w", abs, err) + } + + for _, root := range parentEngine.allowedRoots { + found := false + for _, r := range engine.allowedRoots { + if r == root { + found = true + break + } + } + if !found { + engine.allowedRoots = append(engine.allowedRoots, root) + } + } + + registerFormStubs(engine) + registerMenuStubs(engine) + registerEventStubs(engine) + registerAxBridge(engine) + + err = engine.Execute(string(content)) + if err != nil { + return fmt.Errorf("failed to execute script '%s': %w", abs, err) + } + + if engine.GetMetadataNoSave() { + logs.Success("", "Executed axscript '%s' (nosave)", abs) + return nil + } + + scriptName := engine.GetMetadataName() + if scriptName == "" { + scriptName = filepath.Base(abs) + } + + sm.mu.Lock() + sm.axscriptEngines[abs] = engine + sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ + Name: scriptName, + ScriptType: "axscript", + Path: abs, + }) + sm.mu.Unlock() + + logs.Success("", "Loaded axscript '%s'", abs) + return nil +} + +func (sm *ScriptManager) ImportAxScript(engine *ScriptEngine, scriptPath string) (string, error) { + abs, err := filepath.Abs(scriptPath) + if err != nil { + return "", fmt.Errorf("invalid script path '%s': %w", scriptPath, err) + } + + _, err = engine.ValidatePath(abs) + if err != nil { + return "", err + } + + content, err := os.ReadFile(abs) + if err != nil { + return "", fmt.Errorf("failed to read script '%s': %w", abs, err) + } + + _, execErr := engine.runtime.RunString(string(content)) + if execErr != nil { + return "", fmt.Errorf("failed to import script '%s': %w", abs, execErr) + } + + return abs, nil +} + +/// + func (sm *ScriptManager) executeRegisterCommands(engine *ScriptEngine, agentName string, listenerType string) { engine.mu.Lock() rt := engine.runtime @@ -107,27 +252,25 @@ func (sm *ScriptManager) executeRegisterCommands(engine *ScriptEngine, agentName fn := rt.Get("RegisterCommands") if fn == nil || goja.IsUndefined(fn) { engine.mu.Unlock() - logs.Warn("AxScript", "No RegisterCommands function found in script for '%s'", agentName) + logs.Warn("", "No RegisterCommands function found in script for '%s'", agentName) return } registerFn, ok := goja.AssertFunction(fn) if !ok { engine.mu.Unlock() - logs.Error("AxScript", "RegisterCommands is not a function in script for '%s'", agentName) + logs.Error("", "RegisterCommands is not a function in script for '%s'", agentName) return } result, err := registerFn(goja.Undefined(), rt.ToValue(listenerType)) engine.mu.Unlock() - if err != nil { - logs.Error("AxScript", "Error calling RegisterCommands for '%s': %v", agentName, err) + logs.Error("", "Error calling RegisterCommands for '%s': %v", agentName, err) return } - if result == nil || goja.IsUndefined(result) || goja.IsNull(result) { - logs.Warn("AxScript", "RegisterCommands returned nil for '%s'", agentName) + logs.Warn("", "RegisterCommands returned nil for '%s'", agentName) return } @@ -171,15 +314,18 @@ func (sm *ScriptManager) extractCommandsFromResult(engine *ScriptEngine, agentNa } if groupBuilder == nil { - logs.Warn("AxScript", "Property '%s' for agent '%s' is not a CommandGroup", propName, agentName) + logs.Warn("", "Property '%s' for agent '%s' is not a CommandGroup", propName, agentName) return } group := groupBuilder.ToCommandGroup(agentName) - sm.Registry.RegisterGroups(agentName, listenerType, osType, []CommandGroup{group}, engine) - logs.Debug("AxScript", "Registered %d commands for '%s' listener='%s' os=%s", len(group.Commands), agentName, listenerType, OsToString(osType)) + group.Source = "agent" + sm.CommandStore.RegisterGroups(SourceAgent, agentName, listenerType, osType, []CommandGroup{group}, engine) } +//////////////////// + +// /--- func (sm *ScriptManager) LoadUserScript(name string, script string) error { engine := NewScriptEngine("user:"+name, sm) @@ -201,10 +347,10 @@ func (sm *ScriptManager) LoadUserScript(name string, script string) error { }) sm.mu.Unlock() - logs.Success("AxScript", "Loaded user script '%s'", name) return nil } +// /--- func (sm *ScriptManager) UnloadUserScript(name string) error { sm.mu.Lock() defer sm.mu.Unlock() @@ -215,6 +361,8 @@ func (sm *ScriptManager) UnloadUserScript(name string) error { delete(sm.userEngines, name) + sm.CommandStore.UnregisterByScriptName(SourceUser, name) + for i, info := range sm.scriptInfos { if info.Name == name && info.ScriptType == "user" { sm.scriptInfos = append(sm.scriptInfos[:i], sm.scriptInfos[i+1:]...) @@ -222,10 +370,10 @@ func (sm *ScriptManager) UnloadUserScript(name string) error { } } - logs.Success("AxScript", "Unloaded user script '%s'", name) return nil } +// /--- func (sm *ScriptManager) ListScripts() []ScriptInfo { sm.mu.RLock() defer sm.mu.RUnlock() @@ -235,16 +383,54 @@ func (sm *ScriptManager) ListScripts() []ScriptInfo { return result } +func (sm *ScriptManager) ListProfileScriptsWithContent() []ScriptWithContent { + sm.mu.RLock() + defer sm.mu.RUnlock() + + var result []ScriptWithContent + for _, info := range sm.scriptInfos { + if info.ScriptType == "axscript" && info.Path != "" { + engine, ok := sm.axscriptEngines[info.Path] + if !ok { + continue + } + + var combined strings.Builder + + importedFiles := engine.GetImportedFiles() + for _, importPath := range importedFiles { + importContent, err := os.ReadFile(importPath) + if err != nil { + combined.WriteString(fmt.Sprintf("/* import error: %s */\n", importPath)) + continue + } + combined.WriteString(fmt.Sprintf("/* inlined: %s */\n", filepath.Base(importPath))) + combined.Write(importContent) + combined.WriteString(fmt.Sprintf("\n/* end: %s */\n\n", filepath.Base(importPath))) + } + + mainContent, err := os.ReadFile(info.Path) + if err != nil { + continue + } + combined.Write(mainContent) + + result = append(result, ScriptWithContent{ + Name: info.Name, + Script: combined.String(), + }) + } + } + return result +} + func (sm *ScriptManager) ResolveAndExecutePreHook(agentName string, agentId string, listenerRegName string, os int, cmdline string, args map[string]interface{}) (hookId string, handlerId string, preHookHandled bool, err error) { - resolved, resolveErr := sm.Registry.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) + resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) if resolveErr != nil { return "", "", false, nil } - cmdDef := resolved.Command - if resolved.Subcommand != nil { - cmdDef = resolved.Subcommand - } + cmdDef := resolved.GetEffectiveCommand() if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && resolved.Engine != nil { preHookErr := sm.executePreHook(resolved.Engine, cmdDef.PreHookFunc, agentId, cmdline, args) @@ -265,14 +451,17 @@ func (sm *ScriptManager) ResolveAndExecutePreHook(agentName string, agentId stri return hookId, handlerId, false, nil } +// /--- func (sm *ScriptManager) ParseCommandPublic(cmdline string, resolved *ResolvedCommand) (*ParsedCommand, error) { return ParseCommand(cmdline, resolved) } +// /--- func (sm *ScriptManager) ExecutePreHookPublic(engine *ScriptEngine, fn goja.Callable, agentId string, cmdline string, args map[string]interface{}) error { return sm.executePreHook(engine, fn, agentId, cmdline, args) } +// /--- func (sm *ScriptManager) ResolveFileArgsPublic(engine *ScriptEngine, parsed *ParsedCommand) error { return sm.resolveFileArgs(engine, parsed) } @@ -311,30 +500,48 @@ func (sm *ScriptManager) resolveFileArgs(engine *ScriptEngine, parsed *ParsedCom return nil } -func (sm *ScriptManager) ExecuteCommand(fromEngine *ScriptEngine, agentId string, cmdline string, postHookFn goja.Callable, handlerFn goja.Callable) error { +type agentCommandContext struct { + agentName string + resolved *ResolvedCommand + parsed *ParsedCommand +} + +func (sm *ScriptManager) resolveAgentCommand(agentId string, cmdline string) (*agentCommandContext, error) { if sm.teamserver == nil { - return fmt.Errorf("teamserver not available") + return nil, fmt.Errorf("teamserver not available") } - agentName, os, err := sm.teamserver.AxGetAgentNameById(agentId) + agentName, listenerRegName, os, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { - return err + return nil, err } - listenerRegName, _ := sm.teamserver.AxGetAgentListenerRegName(agentId) - - resolved, resolveErr := sm.Registry.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) + resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) if resolveErr != nil { - return resolveErr + return nil, resolveErr } parsed, parseErr := ParseCommand(cmdline, resolved) if parseErr != nil { - return parseErr + return nil, parseErr + } + + return &agentCommandContext{ + agentName: agentName, + resolved: resolved, + parsed: parsed, + }, nil +} + +// /--- +func (sm *ScriptManager) ExecuteCommand(fromEngine *ScriptEngine, agentId string, cmdline string, postHookFn goja.Callable, handlerFn goja.Callable) error { + ctx, err := sm.resolveAgentCommand(agentId, cmdline) + if err != nil { + return err } if fromEngine != nil { - if fileErr := sm.resolveFileArgs(fromEngine, parsed); fileErr != nil { + if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } @@ -349,44 +556,26 @@ func (sm *ScriptManager) ExecuteCommand(fromEngine *ScriptEngine, agentId string handlerId = sm.HookStore.RegisterHandler(fromEngine, handlerFn, agentId, "server") } - return sm.teamserver.TsAgentCommand(agentName, agentId, "server", hookId, handlerId, cmdline, false, parsed.Args) + return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, cmdline, false, ctx.parsed.Args) } +// /--- func (sm *ScriptManager) ExecuteAlias(fromEngine *ScriptEngine, agentId string, aliasCmdline string) error { - if sm.teamserver == nil { - return fmt.Errorf("teamserver not available") - } - - agentName, os, err := sm.teamserver.AxGetAgentNameById(agentId) + ctx, err := sm.resolveAgentCommand(agentId, aliasCmdline) if err != nil { return err } - listenerRegName, _ := sm.teamserver.AxGetAgentListenerRegName(agentId) - - resolved, resolveErr := sm.Registry.ResolveFromCmdline(agentName, listenerRegName, os, aliasCmdline) - if resolveErr != nil { - return resolveErr - } - - parsed, parseErr := ParseCommand(aliasCmdline, resolved) - if parseErr != nil { - return parseErr - } - if fromEngine != nil { - if fileErr := sm.resolveFileArgs(fromEngine, parsed); fileErr != nil { + if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } - cmdDef := resolved.Command - if resolved.Subcommand != nil { - cmdDef = resolved.Subcommand - } + cmdDef := ctx.resolved.GetEffectiveCommand() - if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && resolved.Engine != nil { - preHookErr := sm.executePreHook(resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, parsed.Args) + if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && ctx.resolved.Engine != nil { + preHookErr := sm.executePreHook(ctx.resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, ctx.parsed.Args) if preHookErr != nil { return preHookErr } @@ -396,51 +585,32 @@ func (sm *ScriptManager) ExecuteAlias(fromEngine *ScriptEngine, agentId string, hookId := "" handlerId := "" - if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && resolved.Engine != nil { - hookId = sm.HookStore.RegisterPostHook(resolved.Engine, cmdDef.PostHookFunc, agentId, "server") + if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && ctx.resolved.Engine != nil { + hookId = sm.HookStore.RegisterPostHook(ctx.resolved.Engine, cmdDef.PostHookFunc, agentId, "server") } - if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && resolved.Engine != nil { - handlerId = sm.HookStore.RegisterHandler(resolved.Engine, cmdDef.HandlerFunc, agentId, "server") + if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && ctx.resolved.Engine != nil { + handlerId = sm.HookStore.RegisterHandler(ctx.resolved.Engine, cmdDef.HandlerFunc, agentId, "server") } - return sm.teamserver.TsAgentCommand(agentName, agentId, "server", hookId, handlerId, aliasCmdline, false, parsed.Args) + return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, aliasCmdline, false, ctx.parsed.Args) } -func (sm *ScriptManager) ExecuteAliasWithHooks(fromEngine *ScriptEngine, agentId string, aliasCmdline string, postHookFn goja.Callable, handlerFn goja.Callable) error { - if sm.teamserver == nil { - return fmt.Errorf("teamserver not available") - } - - agentName, os, err := sm.teamserver.AxGetAgentNameById(agentId) +func (sm *ScriptManager) ExecuteAliasWithHooks(fromEngine *ScriptEngine, agentId string, displayCmdline string, aliasCmdline string, message string, postHookFn goja.Callable, handlerFn goja.Callable) error { + ctx, err := sm.resolveAgentCommand(agentId, aliasCmdline) if err != nil { return err } - listenerRegName, _ := sm.teamserver.AxGetAgentListenerRegName(agentId) - - resolved, resolveErr := sm.Registry.ResolveFromCmdline(agentName, listenerRegName, os, aliasCmdline) - if resolveErr != nil { - return resolveErr - } - - parsed, parseErr := ParseCommand(aliasCmdline, resolved) - if parseErr != nil { - return parseErr - } - if fromEngine != nil { - if fileErr := sm.resolveFileArgs(fromEngine, parsed); fileErr != nil { + if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } - cmdDef := resolved.Command - if resolved.Subcommand != nil { - cmdDef = resolved.Subcommand - } + cmdDef := ctx.resolved.GetEffectiveCommand() - if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && resolved.Engine != nil { - preHookErr := sm.executePreHook(resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, parsed.Args) + if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && ctx.resolved.Engine != nil { + preHookErr := sm.executePreHook(ctx.resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, ctx.parsed.Args) if preHookErr != nil { return preHookErr } @@ -450,20 +620,27 @@ func (sm *ScriptManager) ExecuteAliasWithHooks(fromEngine *ScriptEngine, agentId hookId := "" handlerId := "" - // Explicit hook/handler from caller take priority over command definition if postHookFn != nil { hookId = sm.HookStore.RegisterPostHook(fromEngine, postHookFn, agentId, "server") - } else if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && resolved.Engine != nil { - hookId = sm.HookStore.RegisterPostHook(resolved.Engine, cmdDef.PostHookFunc, agentId, "server") + } else if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && ctx.resolved.Engine != nil { + hookId = sm.HookStore.RegisterPostHook(ctx.resolved.Engine, cmdDef.PostHookFunc, agentId, "server") } if handlerFn != nil { handlerId = sm.HookStore.RegisterHandler(fromEngine, handlerFn, agentId, "server") - } else if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && resolved.Engine != nil { - handlerId = sm.HookStore.RegisterHandler(resolved.Engine, cmdDef.HandlerFunc, agentId, "server") + } else if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && ctx.resolved.Engine != nil { + handlerId = sm.HookStore.RegisterHandler(ctx.resolved.Engine, cmdDef.HandlerFunc, agentId, "server") } - return sm.teamserver.TsAgentCommand(agentName, agentId, "server", hookId, handlerId, aliasCmdline, false, parsed.Args) + cmdlineForDisplay := displayCmdline + if cmdlineForDisplay == "" { + cmdlineForDisplay = aliasCmdline + } + if message != "" { + ctx.parsed.Args["message"] = message + } + + return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, cmdlineForDisplay, false, ctx.parsed.Args) } func (sm *ScriptManager) GetAgents() map[string]interface{} { @@ -480,6 +657,7 @@ func (sm *ScriptManager) GetAgentInfo(agentId string, property string) interface return sm.teamserver.AxGetAgentInfo(agentId, property) } +// /--- func (sm *ScriptManager) GetAgentIds() []string { if sm.teamserver == nil { return []string{} @@ -487,6 +665,7 @@ func (sm *ScriptManager) GetAgentIds() []string { return sm.teamserver.AxGetAgentIds() } +// /--- func (sm *ScriptManager) GetCredentials() []interface{} { if sm.teamserver == nil { return []interface{}{} @@ -494,6 +673,7 @@ func (sm *ScriptManager) GetCredentials() []interface{} { return sm.teamserver.AxGetCredentials() } +// /--- func (sm *ScriptManager) GetTargets() []interface{} { if sm.teamserver == nil { return []interface{}{} @@ -505,11 +685,12 @@ func (sm *ScriptManager) ConsoleMessage(agentId string, msgType int, message str if sm.teamserver == nil { return } - sm.teamserver.TsAgentConsoleOutput(agentId, msgType, message, clearText, false) + sm.teamserver.TsAgentConsoleOutput(agentId, msgType, message, clearText, false) // todo underlaycopy } +// /--- func (sm *ScriptManager) GetCommandsJSON() (string, error) { - allCommands := sm.Registry.GetAllCommands() + allCommands := sm.CommandStore.GetAllCommandsOrdered() data, err := json.Marshal(allCommands) if err != nil { return "", err @@ -517,140 +698,11 @@ func (sm *ScriptManager) GetCommandsJSON() (string, error) { return string(data), nil } +// /--- func (sm *ScriptManager) SetGlobalAllowedRoots(roots []string) { sm.globalAllowedRoots = roots } -func (sm *ScriptManager) LoadAxScript(scriptPath string) error { - abs, err := filepath.Abs(scriptPath) - if err != nil { - return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) - } - - if _, err := os.Stat(abs); err != nil { - return fmt.Errorf("script file not found: %s", abs) - } - - content, err := os.ReadFile(abs) - if err != nil { - return fmt.Errorf("failed to read script '%s': %w", abs, err) - } - - engine, err := NewScriptEngineFromPath(abs, sm) - if err != nil { - return fmt.Errorf("failed to create engine for '%s': %w", abs, err) - } - - registerFormStubs(engine) - registerMenuStubs(engine) - registerEventStubs(engine) - registerAxBridge(engine) - - err = engine.Execute(string(content)) - if err != nil { - return fmt.Errorf("failed to execute script '%s': %w", abs, err) - } - - sm.mu.Lock() - sm.axscriptEngines[abs] = engine - sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ - Name: filepath.Base(abs), - ScriptType: "axscript", - Path: abs, - }) - sm.mu.Unlock() - - logs.Success("AxScript", "Loaded axscript '%s'", abs) - return nil -} - -func (sm *ScriptManager) LoadAxScriptChild(parentEngine *ScriptEngine, scriptPath string) error { - abs, err := filepath.Abs(scriptPath) - if err != nil { - return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) - } - - if _, err := os.Stat(abs); err != nil { - return fmt.Errorf("script file not found: %s", abs) - } - - content, err := os.ReadFile(abs) - if err != nil { - return fmt.Errorf("failed to read script '%s': %w", abs, err) - } - - engine, err := NewScriptEngineFromPath(abs, sm) - if err != nil { - return fmt.Errorf("failed to create engine for '%s': %w", abs, err) - } - - // Child inherits parent's allowed roots - for _, root := range parentEngine.allowedRoots { - found := false - for _, r := range engine.allowedRoots { - if r == root { - found = true - break - } - } - if !found { - engine.allowedRoots = append(engine.allowedRoots, root) - } - } - - registerFormStubs(engine) - registerMenuStubs(engine) - registerEventStubs(engine) - registerAxBridge(engine) - - err = engine.Execute(string(content)) - if err != nil { - return fmt.Errorf("failed to execute script '%s': %w", abs, err) - } - - sm.mu.Lock() - sm.axscriptEngines[abs] = engine - sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ - Name: filepath.Base(abs), - ScriptType: "axscript", - Path: abs, - }) - sm.mu.Unlock() - - logs.Success("AxScript", "Loaded axscript '%s'", abs) - return nil -} - -func (sm *ScriptManager) ImportAxScript(engine *ScriptEngine, scriptPath string) error { - abs, err := filepath.Abs(scriptPath) - if err != nil { - return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) - } - - // Validate path is within engine's allowed roots - _, err = engine.ValidatePath(abs) - if err != nil { - return err - } - - content, err := os.ReadFile(abs) - if err != nil { - return fmt.Errorf("failed to read script '%s': %w", abs, err) - } - - // Execute in the SAME engine context - engine.mu.Lock() - _, execErr := engine.runtime.RunString(string(content)) - engine.mu.Unlock() - - if execErr != nil { - return fmt.Errorf("failed to import script '%s': %w", abs, execErr) - } - - logs.Debug("AxScript", "Imported '%s' into engine '%s'", abs, engine.name) - return nil -} - func (sm *ScriptManager) ReadFileSandboxed(engine *ScriptEngine, path string) ([]byte, error) { validated, err := engine.ValidatePath(path) if err != nil { @@ -659,6 +711,7 @@ func (sm *ScriptManager) ReadFileSandboxed(engine *ScriptEngine, path string) ([ return os.ReadFile(validated) } +// /--- func (sm *ScriptManager) WriteFileSandboxed(engine *ScriptEngine, path string, data []byte, append_ bool) error { validated, err := engine.ValidatePath(path) if err != nil { @@ -683,6 +736,7 @@ func (sm *ScriptManager) WriteFileSandboxed(engine *ScriptEngine, path string, d return err } +// /--- func (sm *ScriptManager) GetDownloads() []interface{} { if sm.teamserver == nil { return []interface{}{} @@ -690,6 +744,7 @@ func (sm *ScriptManager) GetDownloads() []interface{} { return sm.teamserver.AxGetDownloads() } +// /--- func (sm *ScriptManager) GetScreenshots() []interface{} { if sm.teamserver == nil { return []interface{}{} @@ -697,6 +752,7 @@ func (sm *ScriptManager) GetScreenshots() []interface{} { return sm.teamserver.AxGetScreenshots() } +// /--- func (sm *ScriptManager) GetTunnels() []interface{} { if sm.teamserver == nil { return []interface{}{} @@ -704,6 +760,7 @@ func (sm *ScriptManager) GetTunnels() []interface{} { return sm.teamserver.AxGetTunnels() } +// /--- func (sm *ScriptManager) GetInterfaces() []string { if sm.teamserver == nil { return []string{} @@ -711,6 +768,7 @@ func (sm *ScriptManager) GetInterfaces() []string { return sm.teamserver.AxGetInterfaces() } +// /--- func (sm *ScriptManager) GetAgentMark(agentId string) string { if sm.teamserver == nil { return "" @@ -718,6 +776,7 @@ func (sm *ScriptManager) GetAgentMark(agentId string) string { return sm.teamserver.AxGetAgentMark(agentId) } +// /--- func (sm *ScriptManager) UnloadAxScript(name string) error { if sm.teamserver == nil { return fmt.Errorf("teamserver not available") @@ -725,19 +784,18 @@ func (sm *ScriptManager) UnloadAxScript(name string) error { return sm.teamserver.AxUnloadAxScript(name) } +// /--- func (sm *ScriptManager) ValidateCommand(agentId string, cmdline string) (map[string]interface{}, error) { if sm.teamserver == nil { return nil, fmt.Errorf("teamserver not available") } - agentName, osType, err := sm.teamserver.AxGetAgentNameById(agentId) + agentName, listenerRegName, osType, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { return map[string]interface{}{"valid": false, "message": "Agent not found"}, nil } - listenerRegName, _ := sm.teamserver.AxGetAgentListenerRegName(agentId) - - resolved, resolveErr := sm.Registry.ResolveFromCmdline(agentName, listenerRegName, osType, cmdline) + resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, osType, cmdline) if resolveErr != nil { return map[string]interface{}{"valid": false, "message": resolveErr.Error()}, nil } @@ -747,10 +805,7 @@ func (sm *ScriptManager) ValidateCommand(agentId string, cmdline string) (map[st return map[string]interface{}{"valid": false, "message": parseErr.Error()}, nil } - cmdDef := resolved.Command - if resolved.Subcommand != nil { - cmdDef = resolved.Subcommand - } + cmdDef := resolved.GetEffectiveCommand() result := map[string]interface{}{ "valid": true, @@ -763,19 +818,18 @@ func (sm *ScriptManager) ValidateCommand(agentId string, cmdline string) (map[st return result, nil } +// /--- func (sm *ScriptManager) GetCommandNames(agentId string) ([]string, error) { if sm.teamserver == nil { return nil, fmt.Errorf("teamserver not available") } - agentName, osType, err := sm.teamserver.AxGetAgentNameById(agentId) + agentName, listenerRegName, osType, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { return nil, err } - listenerRegName, _ := sm.teamserver.AxGetAgentListenerRegName(agentId) - - groups := sm.Registry.GetCommandsForAgent(agentName, listenerRegName, osType) + groups := sm.CommandStore.GetCommandsForAgent(agentName, listenerRegName, osType) var names []string for _, g := range groups { for _, cmd := range g.Commands { diff --git a/AdaptixServer/core/axscript/registry.go b/AdaptixServer/core/axscript/registry.go index 2f9e8487..8b2807dc 100644 --- a/AdaptixServer/core/axscript/registry.go +++ b/AdaptixServer/core/axscript/registry.go @@ -5,11 +5,264 @@ import ( "sync" ) -type CommandRegistry struct { +type SourceType int + +const ( + SourceAgent SourceType = iota + SourceProfile + SourceUser +) + +func (s SourceType) String() string { + switch s { + case SourceAgent: + return "agent" + case SourceProfile: + return "profile" + case SourceUser: + return "user" + default: + return "unknown" + } +} + +type CommandStore struct { mu sync.RWMutex - groups map[string]map[string]map[int][]CommandGroup // agentName → listener → os → []CommandGroup - engines map[string]*ScriptEngine // agentName → engine + AgentRegistry *CommandRegistry + ProfileRegistry *CommandRegistry + UserRegistry *CommandRegistry +} + +func NewCommandStore() *CommandStore { + return &CommandStore{ + AgentRegistry: NewCommandRegistry(), + ProfileRegistry: NewCommandRegistry(), + UserRegistry: NewCommandRegistry(), + } +} + +func (cs *CommandStore) GetRegistry(source SourceType) *CommandRegistry { + switch source { + case SourceAgent: + return cs.AgentRegistry + case SourceProfile: + return cs.ProfileRegistry + case SourceUser: + return cs.UserRegistry + default: + return nil + } +} + +func (cs *CommandStore) RegisterGroups(source SourceType, agentName string, listener string, os int, groups []CommandGroup, engine *ScriptEngine) { + registry := cs.GetRegistry(source) + if registry != nil { + registry.RegisterGroups(agentName, listener, os, groups, engine) + } +} + +// /--- +func (cs *CommandStore) UnregisterByScriptName(source SourceType, scriptName string) { + registry := cs.GetRegistry(source) + if registry != nil { + registry.UnregisterByScriptName(scriptName) + } +} + +func (cs *CommandStore) Resolve(agentName string, listener string, os int, commandName string) *ResolvedCommand { + if res := cs.AgentRegistry.Resolve(agentName, listener, os, commandName); res != nil { + return res + } + if res := cs.ProfileRegistry.Resolve(agentName, listener, os, commandName); res != nil { + return res + } + if res := cs.UserRegistry.Resolve(agentName, listener, os, commandName); res != nil { + return res + } + return nil +} + +func (cs *CommandStore) ResolveSubcommand(agentName string, listener string, os int, commandName string, subcommandName string) *ResolvedCommand { + if res := cs.AgentRegistry.ResolveSubcommand(agentName, listener, os, commandName, subcommandName); res != nil { + return res + } + if res := cs.ProfileRegistry.ResolveSubcommand(agentName, listener, os, commandName, subcommandName); res != nil { + return res + } + if res := cs.UserRegistry.ResolveSubcommand(agentName, listener, os, commandName, subcommandName); res != nil { + return res + } + return nil +} + +func (cs *CommandStore) ResolveFromCmdline(agentName string, listener string, os int, cmdline string) (*ResolvedCommand, error) { + tokens := Tokenize(cmdline) + if len(tokens) == 0 { + return nil, fmt.Errorf("empty command line") + } + + commandName := tokens[0] + resolved := cs.Resolve(agentName, listener, os, commandName) + if resolved == nil { + return nil, fmt.Errorf("command '%s' not found for agent '%s' os=%d", commandName, agentName, os) + } + + if len(resolved.Command.Subcommands) > 0 { + if len(tokens) < 2 { + return nil, fmt.Errorf("subcommand required for '%s'", commandName) + } + subName := tokens[1] + resolved = cs.ResolveSubcommand(agentName, listener, os, commandName, subName) + if resolved == nil { + return nil, fmt.Errorf("subcommand '%s' not found for command '%s'", subName, commandName) + } + } + + return resolved, nil +} + +// /--- +func (cs *CommandStore) GetCommandsForAgent(agentName string, listener string, os int) []CommandGroup { + var result []CommandGroup + result = append(result, cs.AgentRegistry.GetCommandsForAgent(agentName, listener, os)...) + result = append(result, cs.ProfileRegistry.GetCommandsForAgent(agentName, listener, os)...) + result = append(result, cs.UserRegistry.GetCommandsForAgent(agentName, listener, os)...) + return result +} + +// /--- +func (cs *CommandStore) GetAllCommandsOrdered() []CommandBatch { + var result []CommandBatch + + for agent, listenerMap := range cs.AgentRegistry.GetAllCommands() { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceAgent, + Agent: agent, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + } + + for agent, listenerMap := range cs.ProfileRegistry.GetAllCommands() { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceProfile, + Agent: agent, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + } + + for agent, listenerMap := range cs.UserRegistry.GetAllCommands() { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceUser, + Agent: agent, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + } + + return result +} + +func (cs *CommandStore) GetProfileAndUserCommands() []CommandBatch { + var result []CommandBatch + + for agent, listenerMap := range cs.ProfileRegistry.GetAllCommands() { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceProfile, + Agent: agent, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + } + + for agent, listenerMap := range cs.UserRegistry.GetAllCommands() { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceUser, + Agent: agent, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + } + + return result +} + +func (cs *CommandStore) GetAgentCommandBatches(agentName string) []CommandBatch { + var result []CommandBatch + + listenerMap, exists := cs.AgentRegistry.GetAllCommands()[agentName] + if !exists { + return result + } + + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + if len(groups) > 0 { + result = append(result, CommandBatch{ + Source: SourceAgent, + Agent: agentName, + Listener: listener, + Os: osType, + Groups: groups, + }) + } + } + } + + return result +} + +type CommandBatch struct { + Source SourceType + Agent string + Listener string + Os int + Groups []CommandGroup +} + +//////////////////// + +type CommandRegistry struct { + mu sync.RWMutex + groups map[string]map[string]map[int][]CommandGroup // agentName → listener → os → []CommandGroup + engines map[string]*ScriptEngine // agentName → engine } func NewCommandRegistry() *CommandRegistry { @@ -29,10 +282,14 @@ func (r *CommandRegistry) RegisterGroups(agentName string, listener string, os i if _, ok := r.groups[agentName][listener]; !ok { r.groups[agentName][listener] = make(map[int][]CommandGroup) } + for i := range groups { + groups[i].Engine = engine + } r.groups[agentName][listener][os] = append(r.groups[agentName][listener][os], groups...) r.engines[agentName] = engine } +// /--- func (r *CommandRegistry) UnregisterAgent(agentName string) { r.mu.Lock() defer r.mu.Unlock() @@ -41,14 +298,45 @@ func (r *CommandRegistry) UnregisterAgent(agentName string) { delete(r.engines, agentName) } -func (r *CommandRegistry) resolveInGroups(groups []CommandGroup, engine *ScriptEngine, commandName string) *ResolvedCommand { +// /--- +func (r *CommandRegistry) UnregisterByScriptName(scriptName string) { + r.mu.Lock() + defer r.mu.Unlock() + + for agentName, listenerMap := range r.groups { + for listener, osMap := range listenerMap { + for osType, groups := range osMap { + var filtered []CommandGroup + for _, g := range groups { + if g.ScriptName != scriptName { + filtered = append(filtered, g) + } + } + if len(filtered) == 0 { + delete(osMap, osType) + } else { + r.groups[agentName][listener][osType] = filtered + } + } + if len(osMap) == 0 { + delete(listenerMap, listener) + } + } + if len(listenerMap) == 0 { + delete(r.groups, agentName) + delete(r.engines, agentName) + } + } +} + +func (r *CommandRegistry) resolveInGroups(groups []CommandGroup, commandName string) *ResolvedCommand { for i := range groups { for j := range groups[i].Commands { if groups[i].Commands[j].Name == commandName { return &ResolvedCommand{ Group: &groups[i], Command: &groups[i].Commands[j], - Engine: engine, + Engine: groups[i].Engine, } } } @@ -65,12 +353,10 @@ func (r *CommandRegistry) Resolve(agentName string, listener string, os int, com return nil } - engine := r.engines[agentName] - if listener != "" { if osMap, ok := listenerMap[listener]; ok { if groups, ok := osMap[os]; ok { - if res := r.resolveInGroups(groups, engine, commandName); res != nil { + if res := r.resolveInGroups(groups, commandName); res != nil { return res } } @@ -79,14 +365,14 @@ func (r *CommandRegistry) Resolve(agentName string, listener string, os int, com if osMap, ok := listenerMap[""]; ok { if groups, ok := osMap[os]; ok { - return r.resolveInGroups(groups, engine, commandName) + return r.resolveInGroups(groups, commandName) } } return nil } -func (r *CommandRegistry) resolveSubInGroups(groups []CommandGroup, engine *ScriptEngine, commandName string, subcommandName string) *ResolvedCommand { +func (r *CommandRegistry) resolveSubInGroups(groups []CommandGroup, commandName string, subcommandName string) *ResolvedCommand { for i := range groups { for j := range groups[i].Commands { if groups[i].Commands[j].Name == commandName { @@ -96,7 +382,7 @@ func (r *CommandRegistry) resolveSubInGroups(groups []CommandGroup, engine *Scri Group: &groups[i], Command: &groups[i].Commands[j], Subcommand: &groups[i].Commands[j].Subcommands[k], - Engine: engine, + Engine: groups[i].Engine, } } } @@ -116,12 +402,10 @@ func (r *CommandRegistry) ResolveSubcommand(agentName string, listener string, o return nil } - engine := r.engines[agentName] - if listener != "" { if osMap, ok := listenerMap[listener]; ok { if groups, ok := osMap[os]; ok { - if res := r.resolveSubInGroups(groups, engine, commandName, subcommandName); res != nil { + if res := r.resolveSubInGroups(groups, commandName, subcommandName); res != nil { return res } } @@ -130,39 +414,14 @@ func (r *CommandRegistry) ResolveSubcommand(agentName string, listener string, o if osMap, ok := listenerMap[""]; ok { if groups, ok := osMap[os]; ok { - return r.resolveSubInGroups(groups, engine, commandName, subcommandName) + return r.resolveSubInGroups(groups, commandName, subcommandName) } } return nil } -func (r *CommandRegistry) ResolveFromCmdline(agentName string, listener string, os int, cmdline string) (*ResolvedCommand, error) { - tokens := Tokenize(cmdline) - if len(tokens) == 0 { - return nil, fmt.Errorf("empty command line") - } - - commandName := tokens[0] - resolved := r.Resolve(agentName, listener, os, commandName) - if resolved == nil { - return nil, fmt.Errorf("command '%s' not found for agent '%s' os=%d", commandName, agentName, os) - } - - if len(resolved.Command.Subcommands) > 0 { - if len(tokens) < 2 { - return nil, fmt.Errorf("subcommand required for '%s'", commandName) - } - subName := tokens[1] - resolved = r.ResolveSubcommand(agentName, listener, os, commandName, subName) - if resolved == nil { - return nil, fmt.Errorf("subcommand '%s' not found for command '%s'", subName, commandName) - } - } - - return resolved, nil -} - +// /--- func (r *CommandRegistry) GetCommandsForAgent(agentName string, listener string, os int) []CommandGroup { r.mu.RLock() defer r.mu.RUnlock() @@ -210,6 +469,7 @@ func (r *CommandRegistry) GetAllCommands() map[string]map[string]map[int][]Comma return result } +// /--- func (r *CommandRegistry) HasAgent(agentName string) bool { r.mu.RLock() defer r.mu.RUnlock() diff --git a/AdaptixServer/core/axscript/types.go b/AdaptixServer/core/axscript/types.go index b4031527..ea935e26 100644 --- a/AdaptixServer/core/axscript/types.go +++ b/AdaptixServer/core/axscript/types.go @@ -45,9 +45,12 @@ type CommandDef struct { } type CommandGroup struct { - GroupName string `json:"group_name"` - ScriptName string `json:"script_name"` - Commands []CommandDef `json:"commands"` + GroupName string `json:"group_name"` + GroupDescription string `json:"group_description,omitempty"` + ScriptName string `json:"script_name"` + Commands []CommandDef `json:"commands"` + Source string `json:"source,omitempty"` + Engine *ScriptEngine `json:"-"` } type RegisterCommandsResult struct { @@ -63,6 +66,13 @@ type ResolvedCommand struct { Engine *ScriptEngine } +func (r *ResolvedCommand) GetEffectiveCommand() *CommandDef { + if r.Subcommand != nil { + return r.Subcommand + } + return r.Command +} + type ParsedCommand struct { CommandName string `json:"command"` SubcommandName string `json:"subcommand,omitempty"` @@ -92,6 +102,11 @@ type ScriptInfo struct { Path string `json:"path,omitempty"` } +type ScriptWithContent struct { + Name string `json:"name"` + Script string `json:"script"` +} + func OsFromString(s string) int { switch s { case "windows": diff --git a/AdaptixServer/core/connector/connector.go b/AdaptixServer/core/connector/connector.go index 88a0a848..4dd29157 100644 --- a/AdaptixServer/core/connector/connector.go +++ b/AdaptixServer/core/connector/connector.go @@ -135,19 +135,19 @@ type Teamserver interface { TsAxScriptResolveHooks(agentName string, agentId string, listenerRegName string, os int, cmdline string, args map[string]interface{}) (string, string, bool, error) TsAxScriptIsServerHook(id string) bool TsAxScriptParseAndExecute(agentId string, username string, cmdline string) error - AxGetAgentNameById(agentId string) (string, int, error) - AxGetAgentListenerRegName(agentId string) (string, error) + AxGetAgentContext(agentId string) (agentName string, listenerRegName string, osType int, err error) } type TsConnector struct { - Interface string - Port int - Hash string - OnlyHash bool - Operators map[string]string - Endpoint string - Cert string - Key string + Interface string + Port int + Hash string + OnlyHash bool + Operators map[string]string + Endpoint string + Cert string + Key string + ManagePasswordHash string httpServer *profile.TsHttpServer @@ -265,6 +265,9 @@ func NewTsConnector(ts Teamserver, tsProfile profile.TsProfile, httpServer profi } connector.Key = tsProfile.Key connector.Cert = tsProfile.Cert + if tsProfile.ManagePassword != "" { + connector.ManagePasswordHash = krypt.SHA256([]byte(tsProfile.ManagePassword)) + } if httpServer.Error == nil { httpServer.Error = &profile.TsHttpError{} @@ -392,9 +395,14 @@ func NewTsConnector(ts Teamserver, tsProfile profile.TsProfile, httpServer profi api_group.POST("/tunnel/set/info", connector.TcTunnelSetIno) api_group.GET("/service/list", connector.TcServiceList) - api_group.POST("/service/load", connector.TcServiceLoad) - api_group.POST("/service/unload", connector.TcServiceUnload) + //api_group.POST("/service/load", connector.TcServiceLoad) + //api_group.POST("/service/unload", connector.TcServiceUnload) api_group.POST("/service/call", connector.TcServiceCall) + + //api_group.POST("/axscript/list", connector.TcAxScriptList) + //api_group.POST("/axscript/commands", connector.TcAxScriptCommands) + //api_group.POST("/axscript/load", connector.TcAxScriptLoad) + //api_group.POST("/axscript/unload", connector.TcAxScriptUnload) } connector.Engine.NoRoute(limitTimeoutMiddleware(httpCfg), default404Middleware(httpErr), func(c *gin.Context) { _ = c.Error(errors.New("NoRoute")) }) diff --git a/AdaptixServer/core/connector/tc_agents.go b/AdaptixServer/core/connector/tc_agents.go index af45bebc..024f9647 100644 --- a/AdaptixServer/core/connector/tc_agents.go +++ b/AdaptixServer/core/connector/tc_agents.go @@ -2,6 +2,7 @@ package connector import ( "AdaptixServer/core/utils/logs" + "AdaptixServer/core/utils/std" "encoding/base64" "encoding/json" "errors" @@ -82,9 +83,9 @@ func (tc *TsConnector) resolveFileRefs(args map[string]any) error { } func (tc *TsConnector) dispatchAgentCommand(ctx *gin.Context, username string, commandData *CommandData, args map[string]any) { - agentName, agentOs, nameErr := tc.teamserver.AxGetAgentNameById(commandData.AgentId) - if nameErr != nil { - ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("agent not found: %v", nameErr), "ok": false}) + agentName, listenerRegName, agentOs, ctxErr := tc.teamserver.AxGetAgentContext(commandData.AgentId) + if ctxErr != nil { + ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("agent not found: %v", ctxErr), "ok": false}) return } @@ -96,14 +97,13 @@ func (tc *TsConnector) dispatchAgentCommand(ctx *gin.Context, username string, c /// Resolve server-side hooks if client did not provide any if commandData.HookId == "" && commandData.HandlerId == "" { - listenerRegName, _ := tc.teamserver.AxGetAgentListenerRegName(commandData.AgentId) srvHookId, srvHandlerId, preHookHandled, hookErr := tc.teamserver.TsAxScriptResolveHooks(agentName, commandData.AgentId, listenerRegName, agentOs, commandData.CmdLine, args) if hookErr != nil { - ctx.JSON(http.StatusOK, gin.H{"message": hookErr.Error(), "ok": false}) + tc.teamserver.TsAgentConsoleErrorCommand(commandData.AgentId, username, commandData.CmdLine, std.ExtractJsErrorMessage(hookErr), "", "") + ctx.JSON(http.StatusOK, gin.H{"message": "", "ok": true}) return } if preHookHandled { - tc.teamserver.TsAgentConsoleOutputClient(commandData.AgentId, username, 0, fmt.Sprintf("[AxScript] %s", commandData.CmdLine), "") ctx.JSON(http.StatusOK, gin.H{"message": "", "ok": true}) return } @@ -216,7 +216,6 @@ func (tc *TsConnector) TcAgentCommandFile(ctx *gin.Context) { tc.dispatchAgentCommand(ctx, username, &commandData, args) } - type CommandDataRaw struct { AgentId string `json:"id"` CmdLine string `json:"cmdline"` diff --git a/AdaptixServer/core/connector/tc_axscript.go b/AdaptixServer/core/connector/tc_axscript.go new file mode 100644 index 00000000..a2244fd8 --- /dev/null +++ b/AdaptixServer/core/connector/tc_axscript.go @@ -0,0 +1,97 @@ +package connector + +import ( + "AdaptixServer/core/utils/krypt" + "net/http" + + "github.com/gin-gonic/gin" +) + +type AxScriptLoadData struct { + Name string `json:"name"` + Script string `json:"script"` + ManagePassword string `json:"manage_password"` +} + +type AxScriptUnloadData struct { + Name string `json:"name"` + ManagePassword string `json:"manage_password"` +} + +func (tc *TsConnector) TcAxScriptList(ctx *gin.Context) { + jsonScripts, err := tc.teamserver.TsAxScriptList() + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": err.Error(), "ok": false}) + return + } + ctx.Data(http.StatusOK, "application/json; charset=utf-8", []byte(jsonScripts)) +} + +func (tc *TsConnector) TcAxScriptCommands(ctx *gin.Context) { + jsonCommands, err := tc.teamserver.TsAxScriptCommands() + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": err.Error(), "ok": false}) + return + } + ctx.Data(http.StatusOK, "application/json; charset=utf-8", []byte(jsonCommands)) +} + +func (tc *TsConnector) TcAxScriptLoad(ctx *gin.Context) { + var data AxScriptLoadData + + err := ctx.ShouldBindJSON(&data) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": "invalid JSON data", "ok": false}) + return + } + + if tc.ManagePasswordHash != "" { + if data.ManagePassword == "" || krypt.SHA256([]byte(data.ManagePassword)) != tc.ManagePasswordHash { + ctx.JSON(http.StatusOK, gin.H{"message": "invalid manage_password", "ok": false}) + return + } + } + + if data.Name == "" || data.Script == "" { + ctx.JSON(http.StatusOK, gin.H{"message": "name and script are required", "ok": false}) + return + } + + err = tc.teamserver.TsAxScriptLoadUser(data.Name, data.Script) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": err.Error(), "ok": false}) + return + } + + ctx.JSON(http.StatusOK, gin.H{"message": "", "ok": true}) +} + +func (tc *TsConnector) TcAxScriptUnload(ctx *gin.Context) { + var data AxScriptUnloadData + + err := ctx.ShouldBindJSON(&data) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": "invalid JSON data", "ok": false}) + return + } + + if tc.ManagePasswordHash != "" { + if data.ManagePassword == "" || krypt.SHA256([]byte(data.ManagePassword)) != tc.ManagePasswordHash { + ctx.JSON(http.StatusOK, gin.H{"message": "invalid manage_password", "ok": false}) + return + } + } + + if data.Name == "" { + ctx.JSON(http.StatusOK, gin.H{"message": "name is required", "ok": false}) + return + } + + err = tc.teamserver.TsAxScriptUnloadUser(data.Name) + if err != nil { + ctx.JSON(http.StatusOK, gin.H{"message": err.Error(), "ok": false}) + return + } + + ctx.JSON(http.StatusOK, gin.H{"message": "", "ok": true}) +} diff --git a/AdaptixServer/core/connector/tc_tasks.go b/AdaptixServer/core/connector/tc_tasks.go index 0b1bed7e..2025c0f3 100644 --- a/AdaptixServer/core/connector/tc_tasks.go +++ b/AdaptixServer/core/connector/tc_tasks.go @@ -5,7 +5,7 @@ import ( "net/http" "strconv" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/gin-gonic/gin" ) diff --git a/AdaptixServer/core/database/db_chat.go b/AdaptixServer/core/database/db_chat.go index 720f70c2..1267411f 100644 --- a/AdaptixServer/core/database/db_chat.go +++ b/AdaptixServer/core/database/db_chat.go @@ -5,7 +5,7 @@ import ( "database/sql" "errors" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (dbms *DBMS) DbChatInsert(chatData adaptix.ChatData) error { diff --git a/AdaptixServer/core/database/db_creds.go b/AdaptixServer/core/database/db_creds.go index b85f79aa..c309edae 100644 --- a/AdaptixServer/core/database/db_creds.go +++ b/AdaptixServer/core/database/db_creds.go @@ -8,7 +8,7 @@ import ( "AdaptixServer/core/utils/logs" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (dbms *DBMS) DbCredentialsExist(credsId string) bool { diff --git a/AdaptixServer/core/database/db_screenshots.go b/AdaptixServer/core/database/db_screenshots.go index 6310358b..b01fbc4f 100644 --- a/AdaptixServer/core/database/db_screenshots.go +++ b/AdaptixServer/core/database/db_screenshots.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (dbms *DBMS) DbScreenshotExist(screenId string) bool { diff --git a/AdaptixServer/core/database/db_targets.go b/AdaptixServer/core/database/db_targets.go index cfbfe6a7..a67aa4ce 100644 --- a/AdaptixServer/core/database/db_targets.go +++ b/AdaptixServer/core/database/db_targets.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (dbms *DBMS) DbTargetExist(targetId string) bool { diff --git a/AdaptixServer/core/eventing/evt_types.go b/AdaptixServer/core/eventing/evt_types.go index 9fd35800..d59fc654 100644 --- a/AdaptixServer/core/eventing/evt_types.go +++ b/AdaptixServer/core/eventing/evt_types.go @@ -1,8 +1,6 @@ package eventing -import ( - adaptix "github.com/Adaptix-Framework/axc2" -) +import "github.com/Adaptix-Framework/axc2" type EventType string diff --git a/AdaptixServer/core/extender/ex_agent.go b/AdaptixServer/core/extender/ex_agent.go index f2f4706a..aab06a85 100644 --- a/AdaptixServer/core/extender/ex_agent.go +++ b/AdaptixServer/core/extender/ex_agent.go @@ -1,6 +1,6 @@ package extender -import adaptix "github.com/Adaptix-Framework/axc2" +import "github.com/Adaptix-Framework/axc2" func (ex *AdaptixExtender) ExAgentGenerate(agentName string, generateConfig adaptix.BuildProfile) ([]byte, string, error) { module, err := ex.getAgentModule(agentName) diff --git a/AdaptixServer/core/extender/ex_listener.go b/AdaptixServer/core/extender/ex_listener.go index 94aa6264..1bd622e8 100644 --- a/AdaptixServer/core/extender/ex_listener.go +++ b/AdaptixServer/core/extender/ex_listener.go @@ -1,6 +1,6 @@ package extender -import adaptix "github.com/Adaptix-Framework/axc2" +import "github.com/Adaptix-Framework/axc2" func (ex *AdaptixExtender) ExListenerCreate(listenerName string, configType string, config string, listenerCustomData []byte) (adaptix.ListenerData, []byte, error) { module, err := ex.getListenerModule(configType) diff --git a/AdaptixServer/core/extender/ex_service.go b/AdaptixServer/core/extender/ex_service.go index 2b2bff29..340f079d 100644 --- a/AdaptixServer/core/extender/ex_service.go +++ b/AdaptixServer/core/extender/ex_service.go @@ -6,7 +6,7 @@ import ( "path/filepath" "plugin" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/goccy/go-yaml" ) diff --git a/AdaptixServer/core/extender/extender.go b/AdaptixServer/core/extender/extender.go index 10f333bf..9184a2b7 100644 --- a/AdaptixServer/core/extender/extender.go +++ b/AdaptixServer/core/extender/extender.go @@ -6,7 +6,7 @@ import ( "path/filepath" "plugin" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/goccy/go-yaml" ) diff --git a/AdaptixServer/core/extender/utils.go b/AdaptixServer/core/extender/utils.go index a41ec069..309d3304 100644 --- a/AdaptixServer/core/extender/utils.go +++ b/AdaptixServer/core/extender/utils.go @@ -3,7 +3,7 @@ package extender import ( "errors" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) var ( diff --git a/AdaptixServer/core/profile/utils.go b/AdaptixServer/core/profile/utils.go index 4b75ea26..4510b95d 100644 --- a/AdaptixServer/core/profile/utils.go +++ b/AdaptixServer/core/profile/utils.go @@ -6,18 +6,19 @@ type AdaptixProfile struct { } type TsProfile struct { - Interface string `yaml:"interface"` - Port int `yaml:"port"` - Endpoint string `yaml:"endpoint"` - Password string `yaml:"password"` - OnlyPassword bool `yaml:"only_password"` - Operators map[string]string `yaml:"operators"` - Cert string `yaml:"cert"` - Key string `yaml:"key"` - Extenders []string `yaml:"extenders"` - AxScripts []string `yaml:"axscripts"` - ATokenLive int `yaml:"access_token_live_hours"` - RTokenLive int `yaml:"refresh_token_live_hours"` + Interface string `yaml:"interface"` + Port int `yaml:"port"` + Endpoint string `yaml:"endpoint"` + Password string `yaml:"password"` + ManagePassword string `yaml:"manage_password"` + OnlyPassword bool `yaml:"only_password"` + Operators map[string]string `yaml:"operators"` + Cert string `yaml:"cert"` + Key string `yaml:"key"` + Extenders []string `yaml:"extenders"` + AxScripts []string `yaml:"axscripts"` + ATokenLive int `yaml:"access_token_live_hours"` + RTokenLive int `yaml:"refresh_token_live_hours"` } type TsHttpServer struct { diff --git a/AdaptixServer/core/server/mgr_broker.go b/AdaptixServer/core/server/mgr_broker.go index 8acf765b..d2ce0d3a 100644 --- a/AdaptixServer/core/server/mgr_broker.go +++ b/AdaptixServer/core/server/mgr_broker.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/gorilla/websocket" ) @@ -41,6 +41,7 @@ const ( const ( SyncCategoryExtenders = "extenders" + SyncCategoryScripts = "scripts" SyncCategoryListeners = "listeners" SyncCategoryAgents = "agents" SyncCategoryAgentsOnlyActive = "agents_only_active" diff --git a/AdaptixServer/core/server/mgr_handler_job.go b/AdaptixServer/core/server/mgr_handler_job.go index c42a8752..c74d134b 100644 --- a/AdaptixServer/core/server/mgr_handler_job.go +++ b/AdaptixServer/core/server/mgr_handler_job.go @@ -29,6 +29,13 @@ func (h *JobTaskHandler) Update(tm *TaskManager, agent *Agent, task *adaptix.Tas tm.ts.EventManager.EmitAsync(eventing.EventTaskUpdateJob, event) // ------------- + /// Server-side hook: execute directly + if task.HookId != "" && tm.ts.TsAxScriptIsServerHook(task.HookId) { + h.updateWithServerHook(tm, agent, task, updateData) + return + } + + /// Client-side hook: send to client if task.HookId != "" && task.Client != "" && tm.ts.TsClientConnected(task.Client) { h.updateWithHook(tm, agent, task, updateData) return @@ -37,6 +44,35 @@ func (h *JobTaskHandler) Update(tm *TaskManager, agent *Agent, task *adaptix.Tas h.updateWithoutHook(tm, agent, task, updateData) } +func (h *JobTaskHandler) updateWithServerHook(tm *TaskManager, agent *Agent, task *adaptix.TaskData, updateData *adaptix.TaskData) { + hookData := map[string]interface{}{ + "agent": task.AgentId, + "task_id": task.TaskId, + "message": updateData.Message, + "text": updateData.ClearText, + "type": updateData.MessageType, + "completed": updateData.Completed, + } + result, _ := tm.ts.TsAxScriptExecPostHook(task.HookId, hookData) + if result != nil { + if msg, ok := result["message"].(string); ok { + updateData.Message = msg + } + if txt, ok := result["text"].(string); ok { + updateData.ClearText = txt + } + if mt, ok := result["type"].(int); ok { + updateData.MessageType = mt + } + } + + if updateData.Completed { + tm.ts.TsAxScriptRemovePostHook(task.HookId) + } + + h.updateWithoutHook(tm, agent, task, updateData) +} + func (h *JobTaskHandler) updateWithHook(tm *TaskManager, agent *Agent, task *adaptix.TaskData, updateData *adaptix.TaskData) { updateData.HookId = task.HookId @@ -229,6 +265,7 @@ func (h *JobTaskHandler) finalizeJob(tm *TaskManager, agent *Agent, task *adapti agent.RunningJobs.Delete(task.TaskId) tm.completeTask(agent, task) + tm.executeServerHandler(task) } func (h *JobTaskHandler) OnClientDisconnect(tm *TaskManager, agent *Agent, task *adaptix.TaskData, clientName string) { diff --git a/AdaptixServer/core/server/mgr_handler_task.go b/AdaptixServer/core/server/mgr_handler_task.go index 0afab24e..fb3e8a92 100644 --- a/AdaptixServer/core/server/mgr_handler_task.go +++ b/AdaptixServer/core/server/mgr_handler_task.go @@ -1,8 +1,6 @@ package server -import ( - "github.com/Adaptix-Framework/axc2" -) +import "github.com/Adaptix-Framework/axc2" type TaskTaskHandler struct{} @@ -22,6 +20,42 @@ func (h *TaskTaskHandler) Update(tm *TaskManager, agent *Agent, task *adaptix.Ta task.Message = updateData.Message task.ClearText = updateData.ClearText + if task.HookId != "" && tm.ts.TsAxScriptIsServerHook(task.HookId) { + hookData := map[string]interface{}{ + "agent": task.AgentId, + "task_id": task.TaskId, + "message": task.Message, + "text": task.ClearText, + "type": task.MessageType, + "completed": task.Completed, + } + result, _ := tm.ts.TsAxScriptExecPostHook(task.HookId, hookData) + if result != nil { + if msg, ok := result["message"].(string); ok { + task.Message = msg + } + if txt, ok := result["text"].(string); ok { + task.ClearText = txt + } + if mt, ok := result["type"].(int); ok { + task.MessageType = mt + } + } + + if task.Sync { + if task.Completed { + tm.ts.TsAxScriptRemovePostHook(task.HookId) + task.HookId = "" + tm.completeTask(agent, task) + tm.executeServerHandler(task) + } else { + agent.RunningTasks.Put(task.TaskId, *task) + } + tm.syncTaskUpdate(task.AgentId, agent, task) + } + return + } + if task.HookId != "" && task.Client != "" && tm.ts.TsClientConnected(task.Client) { agent.RunningTasks.Put(task.TaskId, *task) @@ -33,6 +67,7 @@ func (h *TaskTaskHandler) Update(tm *TaskManager, agent *Agent, task *adaptix.Ta if task.Sync { if task.Completed { tm.completeTask(agent, task) + tm.executeServerHandler(task) updateData.HandlerId = task.HandlerId } else { agent.RunningTasks.Put(task.TaskId, *task) diff --git a/AdaptixServer/core/server/mgr_task.go b/AdaptixServer/core/server/mgr_task.go index a4af00f1..c82abb42 100644 --- a/AdaptixServer/core/server/mgr_task.go +++ b/AdaptixServer/core/server/mgr_task.go @@ -115,6 +115,25 @@ func (tm *TaskManager) completeTask(agent *Agent, taskData *adaptix.TaskData) { // ----------------- } +func (tm *TaskManager) executeServerHandler(taskData *adaptix.TaskData) { + if taskData.HandlerId == "" { + return + } + if !tm.ts.TsAxScriptIsServerHook(taskData.HandlerId) { + return + } + handlerData := map[string]interface{}{ + "agent": taskData.AgentId, + "task_id": taskData.TaskId, + "cmdline": taskData.CommandLine, + "message": taskData.Message, + "text": taskData.ClearText, + "type": taskData.MessageType, + } + _ = tm.ts.TsAxScriptExecHandler(taskData.HandlerId, handlerData) + taskData.HandlerId = "" +} + func (tm *TaskManager) Create(agentId string, cmdline string, client string, taskData adaptix.TaskData) { agent, err := tm.getAgent(agentId) if err != nil { @@ -230,6 +249,12 @@ func (tm *TaskManager) Cancel(agentId string, taskId string) error { if found { task, ok := retTask.(adaptix.TaskData) if ok { + if task.HookId != "" && tm.ts.TsAxScriptIsServerHook(task.HookId) { + tm.ts.TsAxScriptRemovePostHook(task.HookId) + } + if task.HandlerId != "" && tm.ts.TsAxScriptIsServerHook(task.HandlerId) { + tm.ts.TsAxScriptRemoveHandler(task.HandlerId) + } packet := CreateSpAgentTaskRemove(task) tm.ts.TsSyncAllClients(packet) } diff --git a/AdaptixServer/core/server/server.go b/AdaptixServer/core/server/server.go index 542aefbb..20211316 100644 --- a/AdaptixServer/core/server/server.go +++ b/AdaptixServer/core/server/server.go @@ -1,6 +1,7 @@ package server import ( + "AdaptixServer/core/axscript" "AdaptixServer/core/connector" "AdaptixServer/core/database" "AdaptixServer/core/eventing" @@ -236,6 +237,8 @@ func (ts *Teamserver) Start() { ts.Extender.LoadPlugins(ts.Profile.Server.Extenders) + ts.TsAxScriptLoadFromProfile() + go ts.AdaptixServer.Start(&stopped) logs.Success("", "Starting server -> https://%s:%v%s", ts.Profile.Server.Interface, ts.Profile.Server.Port, ts.Profile.Server.Endpoint) diff --git a/AdaptixServer/core/server/ts_agent_builder.go b/AdaptixServer/core/server/ts_agent_builder.go index 5a16afd1..ba6c325b 100644 --- a/AdaptixServer/core/server/ts_agent_builder.go +++ b/AdaptixServer/core/server/ts_agent_builder.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/gorilla/websocket" ) diff --git a/AdaptixServer/core/server/ts_axscript.go b/AdaptixServer/core/server/ts_axscript.go index 309b3d52..5dd139a9 100644 --- a/AdaptixServer/core/server/ts_axscript.go +++ b/AdaptixServer/core/server/ts_axscript.go @@ -2,6 +2,7 @@ package server import ( "AdaptixServer/core/utils/logs" + "AdaptixServer/core/utils/std" "encoding/json" "fmt" "net" @@ -9,26 +10,23 @@ import ( adaptix "github.com/Adaptix-Framework/axc2" ) -func (ts *Teamserver) AxGetAgentNameById(agentId string) (string, int, error) { - agent, err := ts.getAgent(agentId) - if err != nil { - return "", 0, err +func (ts *Teamserver) TsAxScriptLoadAgent(agentName string, axScript string, listeners []string) error { + if ts.ScriptManager == nil { + return fmt.Errorf("script manager not initialized") } - data := agent.GetData() - return data.Name, data.Os, nil + return ts.ScriptManager.LoadAgentScript(agentName, axScript, listeners) } -func (ts *Teamserver) AxGetAgentListenerRegName(agentId string) (string, error) { +//////////////////// + +func (ts *Teamserver) AxGetAgentContext(agentId string) (agentName string, listenerRegName string, osType int, err error) { agent, err := ts.getAgent(agentId) if err != nil { - return "", err + return "", "", 0, err } data := agent.GetData() - regName, err := ts.TsListenerRegByName(data.Listener) - if err != nil { - return "", nil - } - return regName, nil + regName, _ := ts.TsListenerRegByName(data.Listener) + return data.Name, regName, data.Os, nil } func (ts *Teamserver) AxGetAgents() map[string]interface{} { @@ -128,6 +126,7 @@ func (ts *Teamserver) AxGetAgentInfo(agentId string, property string) interface{ } } +// /--- func (ts *Teamserver) AxGetAgentIds() []string { var ids []string ts.Agents.ForEach(func(key string, value interface{}) bool { @@ -137,6 +136,7 @@ func (ts *Teamserver) AxGetAgentIds() []string { return ids } +// /--- func (ts *Teamserver) AxGetCredentials() []interface{} { jsonStr, err := ts.TsCredentilsList() if err != nil { @@ -150,6 +150,7 @@ func (ts *Teamserver) AxGetCredentials() []interface{} { return result } +// /--- func (ts *Teamserver) AxGetTargets() []interface{} { jsonStr, err := ts.TsTargetsList() if err != nil { @@ -163,13 +164,7 @@ func (ts *Teamserver) AxGetTargets() []interface{} { return result } -func (ts *Teamserver) TsAxScriptLoadAgent(agentName string, axScript string, listeners []string) error { - if ts.ScriptManager == nil { - return fmt.Errorf("script manager not initialized") - } - return ts.ScriptManager.LoadAgentScript(agentName, axScript, listeners) -} - +// /--- func (ts *Teamserver) TsAxScriptLoadUser(name string, script string) error { if ts.ScriptManager == nil { return fmt.Errorf("script manager not initialized") @@ -177,6 +172,7 @@ func (ts *Teamserver) TsAxScriptLoadUser(name string, script string) error { return ts.ScriptManager.LoadUserScript(name, script) } +// /--- func (ts *Teamserver) TsAxScriptUnloadUser(name string) error { if ts.ScriptManager == nil { return fmt.Errorf("script manager not initialized") @@ -184,6 +180,7 @@ func (ts *Teamserver) TsAxScriptUnloadUser(name string) error { return ts.ScriptManager.UnloadUserScript(name) } +// /--- func (ts *Teamserver) TsAxScriptList() (string, error) { if ts.ScriptManager == nil { return "[]", nil @@ -196,6 +193,7 @@ func (ts *Teamserver) TsAxScriptList() (string, error) { return string(data), nil } +// /--- func (ts *Teamserver) TsAxScriptCommands() (string, error) { if ts.ScriptManager == nil { return "{}", nil @@ -203,19 +201,18 @@ func (ts *Teamserver) TsAxScriptCommands() (string, error) { return ts.ScriptManager.GetCommandsJSON() } +// /--- func (ts *Teamserver) TsAxScriptParseAndExecute(agentId string, username string, cmdline string) error { if ts.ScriptManager == nil { return fmt.Errorf("script manager not initialized") } - agentName, agentOs, err := ts.AxGetAgentNameById(agentId) + agentName, listenerRegName, agentOs, err := ts.AxGetAgentContext(agentId) if err != nil { return fmt.Errorf("agent not found: %w", err) } - listenerRegName, _ := ts.AxGetAgentListenerRegName(agentId) - - resolved, resolveErr := ts.ScriptManager.Registry.ResolveFromCmdline(agentName, listenerRegName, agentOs, cmdline) + resolved, resolveErr := ts.ScriptManager.CommandStore.ResolveFromCmdline(agentName, listenerRegName, agentOs, cmdline) if resolveErr != nil { return fmt.Errorf("unknown command: %w", resolveErr) } @@ -239,9 +236,10 @@ func (ts *Teamserver) TsAxScriptParseAndExecute(agentId string, username string, if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && resolved.Engine != nil { preHookErr := ts.ScriptManager.ExecutePreHookPublic(resolved.Engine, cmdDef.PreHookFunc, agentId, cmdline, parsed.Args) if preHookErr != nil { - return preHookErr + ts.TsAgentConsoleOutputClient(agentId, username, CONSOLE_OUT_LOCAL_ERROR, cmdline, std.ExtractJsErrorMessage(preHookErr)) + return nil } - ts.TsAgentConsoleOutputClient(agentId, username, 0, fmt.Sprintf("[AxScript] %s", cmdline), "") + //ts.TsAgentConsoleOutputClient(agentId, username, 0, fmt.Sprintf("[AxScript] %s", cmdline), "") return nil } @@ -272,6 +270,7 @@ func (ts *Teamserver) TsAxScriptExecPostHook(hookId string, data map[string]inte return ts.ScriptManager.HookStore.ExecutePostHook(hookId, data) } +// /--- func (ts *Teamserver) TsAxScriptExecHandler(handlerId string, data map[string]interface{}) error { if ts.ScriptManager == nil { return nil @@ -286,6 +285,7 @@ func (ts *Teamserver) TsAxScriptRemovePostHook(hookId string) { ts.ScriptManager.HookStore.RemovePostHook(hookId) } +// /--- func (ts *Teamserver) TsAxScriptRemoveHandler(handlerId string) { if ts.ScriptManager == nil { return @@ -300,45 +300,122 @@ func (ts *Teamserver) TsAxScriptIsServerHook(id string) bool { return ts.ScriptManager.HookStore.IsServerHook(id) } -func (ts *Teamserver) TsPresyncAxScriptCommands() []interface{} { +func (ts *Teamserver) TsPresyncAxScriptData() []interface{} { if ts.ScriptManager == nil { return nil } - allCommands := ts.ScriptManager.Registry.GetAllCommands() - var packets []interface{} + scripts := ts.ScriptManager.ListProfileScriptsWithContent() + batches := ts.ScriptManager.CommandStore.GetProfileAndUserCommands() - for agentName, listenerMap := range allCommands { - for listener, osMap := range listenerMap { - for osType, groups := range osMap { - data, err := json.Marshal(groups) - if err != nil { - continue - } - p := CreateSpAxScriptCommands(agentName, listener, osType, string(data)) - packets = append(packets, p) - } + if len(scripts) == 0 && len(batches) == 0 { + return nil + } + + type scriptData struct { + content string + groups []AxCommandBatch + } + scriptsMap := make(map[string]*scriptData) + + for _, s := range scripts { + scriptsMap[s.Name] = &scriptData{ + content: s.Script, + groups: []AxCommandBatch{}, } } + for _, batch := range batches { + if len(batch.Groups) == 0 { + continue + } + + for _, group := range batch.Groups { + scriptName := group.ScriptName + if scriptName == "" { + scriptName = "_unknown_" + } + + data, err := json.Marshal([]interface{}{group}) + if err != nil { + logs.Error("", "Presync marshal error for group '%s': %v", group.GroupName, err) + continue + } + + entry, exists := scriptsMap[scriptName] + if !exists { + entry = &scriptData{ + content: "", + groups: []AxCommandBatch{}, + } + scriptsMap[scriptName] = entry + } + + entry.groups = append(entry.groups, AxCommandBatch{ + Agent: batch.Agent, + Listener: batch.Listener, + Os: batch.Os, + Commands: string(data), + }) + } + } + + var packets []interface{} + for name, data := range scriptsMap { + packets = append(packets, CreateSpAxScriptData(name, data.content, data.groups)) + } return packets } -func (ts *Teamserver) TsAxScriptBroadcastCommands() { - packets := ts.TsPresyncAxScriptCommands() +// /--- +func (ts *Teamserver) TsAxScriptBroadcastData() { + packets := ts.TsPresyncAxScriptData() for _, p := range packets { ts.TsSyncAllClients(p) } } +func (ts *Teamserver) TsGetAgentCommandGroups(agentName string) []AxCommandBatch { + if ts.ScriptManager == nil { + return nil + } + + batches := ts.ScriptManager.CommandStore.GetAgentCommandBatches(agentName) + var result []AxCommandBatch + + for _, batch := range batches { + if len(batch.Groups) == 0 { + continue + } + + data, err := json.Marshal(batch.Groups) + if err != nil { + logs.Error("", "Marshal error for agent '%s': %v", agentName, err) + continue + } + + result = append(result, AxCommandBatch{ + Agent: batch.Agent, + Listener: batch.Listener, + Os: batch.Os, + Commands: string(data), + }) + } + + return result +} + +// /--- func (ts *Teamserver) AxCredentialsAdd(creds []map[string]interface{}) error { return ts.TsCredentilsAdd(creds) } +// /--- func (ts *Teamserver) AxTargetsAdd(targets []map[string]interface{}) error { return ts.TsTargetsAdd(targets) } +// /--- func (ts *Teamserver) AxAgentRemove(agentIds []string) error { for _, id := range agentIds { _ = ts.TsAgentRemove(id) @@ -346,6 +423,7 @@ func (ts *Teamserver) AxAgentRemove(agentIds []string) error { return nil } +// /--- func (ts *Teamserver) AxAgentSetTag(agentIds []string, tag string) error { for _, id := range agentIds { updateData := map[string]interface{}{"tags": tag} @@ -354,6 +432,7 @@ func (ts *Teamserver) AxAgentSetTag(agentIds []string, tag string) error { return nil } +// /--- func (ts *Teamserver) AxAgentSetMark(agentIds []string, mark string) error { for _, id := range agentIds { updateData := map[string]interface{}{"mark": mark} @@ -362,6 +441,7 @@ func (ts *Teamserver) AxAgentSetMark(agentIds []string, mark string) error { return nil } +// /--- func (ts *Teamserver) AxAgentSetColor(agentIds []string, background string, foreground string, reset bool) error { // Agent color is a client-only visual property, no server-side storage return nil @@ -383,13 +463,12 @@ func (ts *Teamserver) TsAxScriptLoadFromProfile() { for _, scriptPath := range ts.Profile.Server.AxScripts { err := ts.ScriptManager.LoadAxScript(scriptPath) if err != nil { - logs.Error("AxScript", "Failed to load profile axscript '%s': %v", scriptPath, err) + logs.Error("", "Failed to load profile axscript '%s': %v", scriptPath, err) } } - - ts.TsAxScriptBroadcastCommands() } +// /--- func (ts *Teamserver) AxGetDownloads() []interface{} { jsonStr, err := ts.TsDownloadList() if err != nil { @@ -403,6 +482,7 @@ func (ts *Teamserver) AxGetDownloads() []interface{} { return result } +// /--- func (ts *Teamserver) AxGetScreenshots() []interface{} { jsonStr, err := ts.TsScreenshotList() if err != nil { @@ -416,6 +496,7 @@ func (ts *Teamserver) AxGetScreenshots() []interface{} { return result } +// /--- func (ts *Teamserver) AxGetTunnels() []interface{} { jsonStr, err := ts.TsTunnelList() if err != nil { @@ -429,6 +510,7 @@ func (ts *Teamserver) AxGetTunnels() []interface{} { return result } +// /--- func (ts *Teamserver) AxGetInterfaces() []string { var result []string ifaces, err := net.Interfaces() @@ -456,6 +538,7 @@ func (ts *Teamserver) AxGetInterfaces() []string { return result } +// /--- func (ts *Teamserver) AxGetAgentMark(agentId string) string { agent, err := ts.getAgent(agentId) if err != nil { @@ -465,6 +548,7 @@ func (ts *Teamserver) AxGetAgentMark(agentId string) string { return data.Mark } +// /--- func (ts *Teamserver) AxUnloadAxScript(name string) error { if ts.ScriptManager == nil { return fmt.Errorf("script manager not initialized") diff --git a/AdaptixServer/core/server/ts_chat.go b/AdaptixServer/core/server/ts_chat.go index 9288ec30..307e1a9d 100644 --- a/AdaptixServer/core/server/ts_chat.go +++ b/AdaptixServer/core/server/ts_chat.go @@ -4,7 +4,7 @@ import ( "AdaptixServer/core/utils/logs" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsChatSendMessage(username string, message string) { diff --git a/AdaptixServer/core/server/ts_clients.go b/AdaptixServer/core/server/ts_clients.go index ac6842bd..5f2af040 100644 --- a/AdaptixServer/core/server/ts_clients.go +++ b/AdaptixServer/core/server/ts_clients.go @@ -15,6 +15,7 @@ func (ts *Teamserver) TsClientExists(username string) bool { var InitialSyncCategories = []string{ SyncCategoryExtenders, + SyncCategoryScripts, SyncCategoryListeners, SyncCategoryAgents, SyncCategoryPivots, diff --git a/AdaptixServer/core/server/ts_creds.go b/AdaptixServer/core/server/ts_creds.go index b5ba8fa4..876f5998 100644 --- a/AdaptixServer/core/server/ts_creds.go +++ b/AdaptixServer/core/server/ts_creds.go @@ -7,7 +7,7 @@ import ( "math/rand/v2" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsCredentilsList() (string, error) { diff --git a/AdaptixServer/core/server/ts_notify.go b/AdaptixServer/core/server/ts_notify.go index 7862fbfe..94a820aa 100644 --- a/AdaptixServer/core/server/ts_notify.go +++ b/AdaptixServer/core/server/ts_notify.go @@ -5,7 +5,7 @@ import ( "strings" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsNotifyClient(connected bool, username string) { diff --git a/AdaptixServer/core/server/ts_plugins.go b/AdaptixServer/core/server/ts_plugins.go index d2f7cca6..fec70da9 100644 --- a/AdaptixServer/core/server/ts_plugins.go +++ b/AdaptixServer/core/server/ts_plugins.go @@ -2,11 +2,12 @@ package server import ( "AdaptixServer/core/extender" + "AdaptixServer/core/utils/logs" isvalid "AdaptixServer/core/utils/valid" "errors" "fmt" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsListenerReg(listenerInfo extender.ListenerInfo) error { @@ -59,11 +60,8 @@ func (ts *Teamserver) TsAgentReg(agentInfo extender.AgentInfo) error { err := ts.TsAxScriptLoadAgent(agentInfo.Name, agentInfo.AX, agentInfo.Listeners) if err != nil { logs.Warn("", "Agent %s: AxScript load failed (commands will come from client): %v", agentInfo.Name, err) - } else { - ts.TsAxScriptBroadcastCommands() } } - return nil } diff --git a/AdaptixServer/core/server/ts_screenshots.go b/AdaptixServer/core/server/ts_screenshots.go index 116a6a1a..96a8ae7a 100644 --- a/AdaptixServer/core/server/ts_screenshots.go +++ b/AdaptixServer/core/server/ts_screenshots.go @@ -11,7 +11,7 @@ import ( "os" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsScreenshotList() (string, error) { diff --git a/AdaptixServer/core/server/ts_sync.go b/AdaptixServer/core/server/ts_sync.go index 3f5b9caf..30bb41c8 100644 --- a/AdaptixServer/core/server/ts_sync.go +++ b/AdaptixServer/core/server/ts_sync.go @@ -21,7 +21,7 @@ func (ts *Teamserver) TsClientConnected(username string) bool { func getPacketCategory(packet interface{}) string { switch packet.(type) { - case SyncPackerListenerReg, SyncPackerAgentReg, SyncPackerServiceReg, SyncPackerAxScriptCommands: + case SyncPackerListenerReg, SyncPackerAgentReg, SyncPackerServiceReg, SyncPackerAxScriptData: return "extenders" case SyncPackerListenerStart: return "listeners" @@ -101,13 +101,15 @@ func (ts *Teamserver) TsSyncCategories(client *ClientHandler, categories []strin if requested[SyncCategoryExtenders] { delete(requested, SyncCategoryExtenders) packets = append(packets, ts.TsPresyncExtenders()...) - packets = append(packets, ts.TsPresyncAxScriptCommands()...) + } + if requested[SyncCategoryScripts] { + delete(requested, SyncCategoryScripts) + packets = append(packets, ts.TsPresyncAxScriptData()...) } if requested[SyncCategoryListeners] { delete(requested, SyncCategoryListeners) packets = append(packets, ts.TsPresyncListeners()...) } - if requested[SyncCategoryAgents] { delete(requested, SyncCategoryAgents) delete(requested, SyncCategoryAgentsOnlyActive) @@ -255,7 +257,8 @@ func (ts *Teamserver) TsPresyncExtenders() []interface{} { ts.agent_configs.ForEach(func(key string, value interface{}) bool { agentInfo := value.(extender.AgentInfo) - p := CreateSpAgentReg(agentInfo.Name, agentInfo.AX, agentInfo.Listeners, agentInfo.MultiListeners) + groups := ts.TsGetAgentCommandGroups(agentInfo.Name) + p := CreateSpAgentReg(agentInfo.Name, agentInfo.AX, agentInfo.Listeners, agentInfo.MultiListeners, groups) packets = append(packets, p) return true }) diff --git a/AdaptixServer/core/server/ts_syncpacket.go b/AdaptixServer/core/server/ts_syncpacket.go index 0bf2cbe0..18a29802 100644 --- a/AdaptixServer/core/server/ts_syncpacket.go +++ b/AdaptixServer/core/server/ts_syncpacket.go @@ -27,21 +27,21 @@ const ( TYPE_SYNC_CATEGORY_BATCH = 0x15 TYPE_CHAT_MESSAGE = 0x18 + TYPE_SERVICE_DATA = 0x19 - TYPE_SERVICE_REG = 0x21 - TYPE_SERVICE_DATA = 0x22 + TYPE_LISTENER_REG = 0x21 + TYPE_AGENT_REG = 0x22 + TYPE_SERVICE_REG = 0x23 - TYPE_LISTENER_REG = 0x31 - TYPE_LISTENER_START = 0x32 + TYPE_LISTENER_START = 0x31 + TYPE_LISTENER_EDIT = 0x32 TYPE_LISTENER_STOP = 0x33 - TYPE_LISTENER_EDIT = 0x34 - TYPE_AGENT_REG = 0x41 - TYPE_AGENT_NEW = 0x42 - TYPE_AGENT_TICK = 0x43 - TYPE_AGENT_UPDATE = 0x44 + TYPE_AGENT_NEW = 0x41 + TYPE_AGENT_UPDATE = 0x42 + TYPE_AGENT_REMOVE = 0x43 + TYPE_AGENT_TICK = 0x44 TYPE_AGENT_LINK = 0x45 - TYPE_AGENT_REMOVE = 0x46 TYPE_AGENT_TASK_SYNC = 0x49 TYPE_AGENT_TASK_UPDATE = 0x4a @@ -179,7 +179,7 @@ func CreateSpListenerStop(name string) SyncPackerListenerStop { /// AGENT -func CreateSpAgentReg(agent string, ax string, listeners []string, multiListeners bool) SyncPackerAgentReg { +func CreateSpAgentReg(agent string, ax string, listeners []string, multiListeners bool, groups []AxCommandBatch) SyncPackerAgentReg { return SyncPackerAgentReg{ SpType: TYPE_AGENT_REG, @@ -187,6 +187,7 @@ func CreateSpAgentReg(agent string, ax string, listeners []string, multiListener AX: ax, Listeners: listeners, MultiListeners: multiListeners, + Groups: groups, } } @@ -756,13 +757,11 @@ func CreateSpServiceData(service string, data string) SyncPackerServiceData { /// AXSCRIPT -func CreateSpAxScriptCommands(agent string, listener string, osType int, commandsJson string) SyncPackerAxScriptCommands { - return SyncPackerAxScriptCommands{ - SpType: TYPE_AXSCRIPT_COMMANDS, - - Agent: agent, - Listener: listener, - Os: osType, - Commands: commandsJson, +func CreateSpAxScriptData(name string, content string, groups []AxCommandBatch) SyncPackerAxScriptData { + return SyncPackerAxScriptData{ + SpType: TYPE_AXSCRIPT_COMMANDS, + Name: name, + Content: content, + Groups: groups, } } diff --git a/AdaptixServer/core/server/ts_targets.go b/AdaptixServer/core/server/ts_targets.go index 0d8396fa..88a56082 100644 --- a/AdaptixServer/core/server/ts_targets.go +++ b/AdaptixServer/core/server/ts_targets.go @@ -8,7 +8,7 @@ import ( "math/rand/v2" "time" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsTargetsList() (string, error) { diff --git a/AdaptixServer/core/server/utils.go b/AdaptixServer/core/server/utils.go index 815785d0..037cb916 100644 --- a/AdaptixServer/core/server/utils.go +++ b/AdaptixServer/core/server/utils.go @@ -273,10 +273,11 @@ type SyncPackerListenerStop struct { type SyncPackerAgentReg struct { SpType int `json:"type"` - Agent string `json:"agent"` - AX string `json:"ax"` - Listeners []string `json:"listeners"` - MultiListeners bool `json:"multi_listeners"` + Agent string `json:"agent"` + AX string `json:"ax"` + Listeners []string `json:"listeners"` + MultiListeners bool `json:"multi_listeners"` + Groups []AxCommandBatch `json:"groups"` } /// SERVICE @@ -754,9 +755,14 @@ type SyncPackerTunnelDelete struct { TunnelId string `json:"p_tunnel_id"` } -type SyncPackerAxScriptCommands struct { - SpType int `json:"type"` +type SyncPackerAxScriptData struct { + SpType int `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + Groups []AxCommandBatch `json:"groups"` +} +type AxCommandBatch struct { Agent string `json:"agent"` Listener string `json:"listener"` Os int `json:"os"` diff --git a/AdaptixServer/core/utils/fsystem/fs.go b/AdaptixServer/core/utils/fsystem/fs.go new file mode 100644 index 00000000..14aad12e --- /dev/null +++ b/AdaptixServer/core/utils/fsystem/fs.go @@ -0,0 +1,23 @@ +package fsystem + +import ( + "os" + "path/filepath" +) + +func ResolveRealPath(path string) (string, error) { + info, err := os.Lstat(path) + if err != nil { + return filepath.Clean(path), nil + } + + if info.Mode()&os.ModeSymlink != 0 { + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + return "", err + } + return filepath.Clean(resolved), nil + } + + return filepath.Clean(path), nil +} diff --git a/AdaptixServer/core/utils/proxy/socks.go b/AdaptixServer/core/utils/proxy/socks.go index c269cf76..dc9407fe 100644 --- a/AdaptixServer/core/utils/proxy/socks.go +++ b/AdaptixServer/core/utils/proxy/socks.go @@ -7,7 +7,7 @@ import ( "io" "net" - adaptix "github.com/Adaptix-Framework/axc2" + "github.com/Adaptix-Framework/axc2" "github.com/gorilla/websocket" ) diff --git a/AdaptixServer/core/utils/std/std.go b/AdaptixServer/core/utils/std/std.go index 5179f43b..7fac05e6 100644 --- a/AdaptixServer/core/utils/std/std.go +++ b/AdaptixServer/core/utils/std/std.go @@ -34,3 +34,19 @@ func DifferenceStringsArray(a, b []string) []string { } return result } + +func ExtractJsErrorMessage(err error) string { + if err == nil { + return "" + } + msg := err.Error() + if idx := strings.Index(msg, "Error: "); idx != -1 { + msg = msg[idx+7:] + } else if idx := strings.LastIndex(msg, "GoError: "); idx != -1 { + msg = msg[idx+9:] + } + if idx := strings.Index(msg, " at "); idx != -1 { + msg = msg[:idx] + } + return msg +} diff --git a/AdaptixServer/go.work.sum b/AdaptixServer/go.work.sum index 5c7f6331..a6f3b759 100644 --- a/AdaptixServer/go.work.sum +++ b/AdaptixServer/go.work.sum @@ -1,5 +1,3 @@ -github.com/Adaptix-Framework/axc2 v1.1.3 h1:2Y28y0PMiX2v1EReK95Qe2TYoaOJfeYNfiMP/L8WGYE= -github.com/Adaptix-Framework/axc2 v1.1.3/go.mod h1:3oJyFeRVIql1RTsNa0meEqK3+P+6JTAMMjMdVyXhbaQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -22,13 +20,11 @@ github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= @@ -36,8 +32,6 @@ github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e h1:a+PGEeXb+e github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -56,8 +50,6 @@ github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5E github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -153,8 +145,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= nullprogram.com/x/optparse v1.0.0 h1:xGFgVi5ZaWOnYdac2foDT3vg0ZZC9ErXFV57mr4OHrI= diff --git a/AdaptixServer/profile.yaml b/AdaptixServer/profile.yaml index 11da50bb..363b546b 100644 --- a/AdaptixServer/profile.yaml +++ b/AdaptixServer/profile.yaml @@ -17,6 +17,8 @@ Teamserver: - "extenders/beacon_agent/config.yaml" - "extenders/gopher_listener_tcp/config.yaml" - "extenders/gopher_agent/config.yaml" + axscripts: +# - "Extension-Kit/extension-kit.axs" access_token_live_hours: 12 refresh_token_live_hours: 168