#include #include QString serializeParam(const QString &token) { QString result = token; result.replace("\\", "\\\\"); result.replace("\"", "\\\""); if (result.contains(' ')) { result = "\"" + result + "\""; } return result; } QStringList unserializeParams(const QString &commandline) { QStringList tokens; QString token; bool inQuotes = false; int len = commandline.length(); for (int i = 0; i < len; ) { QChar c = commandline[i]; if (c.isSpace() && !inQuotes) { if (!token.isEmpty()) { tokens << token; token.clear(); } ++i; continue; } /* If we encounter a double quote */ if (c == '"') { inQuotes = !inQuotes; ++i; continue; } /* If we encounter a backslash, handle escape sequences */ if (c == '\\') { int numBS = 0; /*Count the number of consecutive backslashes*/ while (i < len && commandline[i] == '\\') { ++numBS; ++i; } /*Check if the next character is a double quote*/ if (i < len && commandline[i] == '"') { /*Append half the number of backslashes (integer division)*/ token.append(QString(numBS / 2, '\\')); if (numBS % 2 == 0) { /*Even number of backslashes: the quote is not escaped, so it toggles the quote state*/ inQuotes = !inQuotes; } else { /*Odd number of backslashes: the quote is escaped, add it to the token*/ token.append('"'); } ++i; } else { /*No double quote after backslashes: all backslashes are literal*/ token.append(QString(numBS, '\\')); } continue; } token.append(c); ++i; } if (!token.isEmpty()) tokens << token; return tokens; } Commander::Commander(){} Commander::~Commander() = default; void Commander::AddRegCommands(const CommandsGroup &group) { regCommandsGroup = group; } void Commander::AddAxCommands(const QString &groupName, const QList &axCommands, QJSEngine *engine) { CommandsGroup newGroup = {groupName, axCommands}; axCommandsGroup.append(newGroup); } void Commander::RemoveAxCommands(const QString &filepath) { // extModules.remove(filepath); } CommanderResult Commander::ProcessInput(QString agentId, QString cmdline) { QStringList parts = unserializeParams(cmdline); if (parts.isEmpty()) return CommanderResult{false, true, ""}; QString commandName = parts[0]; parts.removeAt(0); 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() ) { return ProcessCommand(command, parts, jsonObj); } else { if ( parts.isEmpty() ) return CommanderResult{true, true, "Subcommand must be set"}; QString subCommandName = parts[0]; parts.removeAt(0); for (Command subcommand : command.subcommands) { if (subCommandName == subcommand.name) { jsonObj["subcommand"] = subcommand.name; return ProcessCommand(subcommand, parts, jsonObj); } } return CommanderResult{true, true, "Subcommand not found"}; } } } } for (Command command : regCommandsGroup.commands) { if (command.name == commandName) { QJsonObject jsonObj; jsonObj["command"] = command.name; if ( command.subcommands.isEmpty() ) { if (command.is_pre_hook) { ProcessPreHook(regCommandsGroup.engine, agentId, command, cmdline, parts); return CommanderResult{false, false, "", {}, true }; } else { return ProcessCommand(command, parts, jsonObj); } } else { if ( parts.isEmpty() ) return CommanderResult{true, true, "Subcommand must be set" }; QString subCommandName = parts[0]; parts.removeAt(0); for (Command subcommand : command.subcommands) { if (subCommandName == subcommand.name) { jsonObj["subcommand"] = subcommand.name; if (subcommand.is_pre_hook) { ProcessPreHook(regCommandsGroup.engine, agentId, subcommand, cmdline, parts); return CommanderResult{false, false, "", {}, true }; } else { return ProcessCommand(subcommand, parts, jsonObj); } } } return CommanderResult{true, true, "Subcommand not found" }; } } } return CommanderResult{true, true, "Command not found"}; } void Commander::ProcessPreHook(QJSEngine *engine, const QString &agentId, const Command &command, const QString &cmdline, QStringList args) { if (!engine) return; QList jsArgs; jsArgs << engine->toScriptValue(agentId); jsArgs << engine->toScriptValue(cmdline); for (const QString& arg : args) { jsArgs << engine->toScriptValue(arg); } QJSValue result = command.pre_hook.call(jsArgs); if (result.isError()) { qWarning() << "JS error:" << result.property("message").toString(); qWarning() << "Stack trace:" << result.property("stack").toString(); } } CommanderResult Commander::ProcessCommand(Command command, QStringList args, QJsonObject jsonObj) { QMap parsedArgsMap; QString wideKey; for (int i = 0; i < args.size(); ++i) { QString arg = args[i]; bool isWideArgs = true; for (Argument commandArg : command.args) { if (commandArg.flag) { if (commandArg.type == "BOOL" && commandArg.mark == arg) { parsedArgsMap[commandArg.mark] = "true"; wideKey = commandArg.mark; isWideArgs = false; break; } else if (commandArg.mark == arg && args.size() > i + 1) { ++i; parsedArgsMap[commandArg.name] = args[i]; wideKey = commandArg.name; isWideArgs = false; break; } } else if (!parsedArgsMap.contains(commandArg.name)) { parsedArgsMap[commandArg.name] = arg; wideKey = commandArg.name; isWideArgs = false; break; } } if( isWideArgs ) { QString wideStr; for(int j = i; j < args.size(); ++j) { wideStr += " " + args[j]; } parsedArgsMap[wideKey] += wideStr; break; } } for (Argument commandArg : command.args) { if (parsedArgsMap.contains(commandArg.name) || parsedArgsMap.contains(commandArg.mark)) { if (commandArg.type == "STRING") { jsonObj[commandArg.name] = parsedArgsMap[commandArg.name]; } else if (commandArg.type == "INT") { jsonObj[commandArg.name] = parsedArgsMap[commandArg.name].toInt(); } else if (commandArg.type == "BOOL") { jsonObj[commandArg.mark] = parsedArgsMap[commandArg.mark] == "true"; } else if (commandArg.type == "FILE") { QString path = parsedArgsMap[commandArg.name]; if (path.startsWith("~/")) path = QDir::home().filePath(path.mid(2)); QFile file(path); if (file.open(QIODevice::ReadOnly)) { QByteArray fileData = file.readAll(); jsonObj[commandArg.name] = QString::fromLatin1(fileData.toBase64()); file.close(); } else { return CommanderResult{true, true, "Failed to open file: " + path}; } } } else if (commandArg.required) { if ( (commandArg.defaultValue.isNull() || !commandArg.defaultValue.isValid()) && !commandArg.defaultUsed) { return CommanderResult{true, true, "Missing required argument: " + commandArg.name}; } else { if (commandArg.type == "STRING" && commandArg.defaultValue.typeId() == QMetaType::QString) { jsonObj[commandArg.name] = commandArg.defaultValue.toString(); } else if (commandArg.type == "INT" && commandArg.defaultValue.typeId() == QMetaType::Int) { jsonObj[commandArg.name] = commandArg.defaultValue.toInt(); } else if (commandArg.type == "BOOL" && commandArg.defaultValue.typeId() == QMetaType::Bool) { jsonObj[commandArg.mark] = commandArg.defaultValue.toBool(); } else { return CommanderResult{true, true, "Missing required argument: " + commandArg.name}; } } } } QString msg = command.message; if( !msg.isEmpty() ) { for ( QString k : parsedArgsMap.keys() ) { QString param = "<" + k + ">"; if( msg.contains(param) ) msg = msg.replace(param, parsedArgsMap[k]); } jsonObj["message"] = msg; } return CommanderResult{false, false, "", jsonObj }; } QString Commander::GetError() { return error; } CommanderResult Commander::ProcessHelp(QStringList commandParts) { QString result; QTextStream output(&result); if (commandParts.isEmpty()) { int TotalWidth = 24; output << QString("\n"); output << QString(" Command Description\n"); output << QString(" ------- -----------\n"); for (auto command : regCommandsGroup.commands) { QString commandName = command.name; if (!command.subcommands.isEmpty()) commandName += '*'; QString tab = QString(TotalWidth - commandName.size(), ' '); 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 ( auto command : script_group.commands ) { QString commandName = command.name; if ( command.subcommands.isEmpty() ) { QString tab = QString(TotalWidth - commandName.size(), ' '); output << " " + commandName + tab + " " + command.description + "\n"; } else { for ( auto subcmd : command.subcommands ) { QString subcmdName = commandName + " " + subcmd.name; QString tab = QString(TotalWidth - subcmdName.size(), ' '); output << " " + subcmdName + tab + " " + subcmd.description + "\n"; } } } } return CommanderResult{false, true, result}; } else { Command foundCommand; QString commandName = commandParts[0]; for (Command cmd : regCommandsGroup.commands) { if (cmd.name == commandName) { foundCommand = cmd; break; } } for(auto script_group : axCommandsGroup) { if ( !foundCommand.name.isEmpty() ) break; for (Command cmd : script_group.commands) { if (cmd.name == commandName) { foundCommand = cmd; break; } } } if ( foundCommand.name.isEmpty() ) return CommanderResult{true, true, "Unknown command: " + commandName}; if (commandParts.size() == 1) { output << QString("\n"); output << " Command : " + foundCommand.name + "\n"; if(!foundCommand.description.isEmpty()) output << " Description : " + foundCommand.description + "\n"; if(!foundCommand.example.isEmpty()) output << " Example : " + foundCommand.example + "\n"; if( !foundCommand.subcommands.isEmpty() ) { output << "\n"; output << " SubCommand Description\n"; output << " ---------- -----------\n"; for ( auto subcmd : foundCommand.subcommands ) { int TotalWidth = 20; int cmdWidth = subcmd.name.size(); if (cmdWidth > TotalWidth) cmdWidth = TotalWidth; QString tab = QString(TotalWidth - cmdWidth, ' '); output << " " + subcmd.name + tab + " " + subcmd.description + "\n"; } } else if (!foundCommand.args.isEmpty()) { QString usageHelp; QTextStream usageStream(&usageHelp); usageStream << foundCommand.name; int maxArgLength = 0; for (const auto &arg : foundCommand.args) { QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]"); maxArgLength = qMax(maxArgLength, fullarg.size()); usageStream << " " + fullarg; } output << " Usage : " + usageHelp + "\n\n"; output << " Arguments:\n"; for (const auto &arg : foundCommand.args) { QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]"); QString padding = QString(maxArgLength - fullarg.size(), ' '); output << " " + fullarg + padding + " : " + arg.type + (arg.defaultUsed ? " (default: '" + arg.defaultValue.toString() + "'). " : ". ") + arg.description + "\n"; } } } else if (commandParts.size() == 2) { Command foundSubCommand; QString subCommandName = commandParts[1]; for (Command subcmd : foundCommand.subcommands) { if (subcmd.name == subCommandName) { foundSubCommand = subcmd; break; } } if ( foundSubCommand.name.isEmpty() ) return CommanderResult{true, true, "Unknown subcommand: " + subCommandName}; output << " Command : " + foundCommand.name + " " + foundSubCommand.name +"\n"; if(!foundSubCommand.description.isEmpty()) output << " Description : " + foundSubCommand.description + "\n"; if(!foundSubCommand.example.isEmpty()) output << " Example : " + foundSubCommand.example + "\n"; if (!foundSubCommand.args.isEmpty()) { QString usageHelp; QTextStream usageStream(&usageHelp); usageStream << foundCommand.name + " " + foundSubCommand.name; int maxArgLength = 0; for (const auto &arg : foundSubCommand.args) { QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]"); maxArgLength = qMax(maxArgLength, fullarg.size()); usageStream << " " + fullarg; } output << " Usage : " + usageHelp + "\n\n"; output << " Arguments:\n"; for (const auto &arg : foundSubCommand.args) { QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]"); QString padding = QString(maxArgLength - fullarg.size(), ' '); output << " " + fullarg + padding + " : " + arg.type + (arg.defaultUsed ? " (default: '" + arg.defaultValue.toString() + "'). " : ". ") + arg.description + "\n"; } } } else { return CommanderResult{true, true, "Error Help format: 'help [command [subcommand]]'"}; } return CommanderResult{false, true, output.readAll()}; } } QStringList Commander::GetCommands() { QStringList commandList; QStringList helpCommandList; for (Command cmd : regCommandsGroup.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( 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; return commandList; }