Files
SimpleRemoter/server/2015Remote/CPasswordDlg.cpp
2026-04-19 22:55:21 +02:00

1212 lines
45 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// CPasswordDlg.cpp: 实现文件
//
#include "stdafx.h"
#include "CPasswordDlg.h"
#include "CLicenseDlg.h"
#include "afxdialogex.h"
#include "pwd_gen.h"
#include "2015Remote.h"
#include "common/skCrypter.h"
#include "2015RemoteDlg.h"
#include "LicenseFile.h"
#include "generated_hash.h"
#include "InputDlg.h"
#include "FrpsForSubDlg.h"
#include "UIBranding.h"
// 外部函数声明
extern std::vector<std::string> splitString(const std::string& str, char delimiter);
extern std::string GetFirstMasterIP(const std::string& master);
// CPasswordDlg 对话框
IMPLEMENT_DYNAMIC(CPasswordDlg, CDialogEx)
void WriteHash(const char* pwdHash, const char* upperHash) {
if (strlen(pwdHash) == 0 || strlen(upperHash) == 0) {
return;
}
memcpy(g_UpperHash + 100, upperHash, 64);
std::string id = genHMACbyHash(pwdHash, upperHash);
Validation verify(365, "", 0, id.c_str());
WritePwdHash(g_MasterID, pwdHash, verify);
}
std::string getUpperHash()
{
return std::string(g_UpperHash + 100, 64);
}
std::string GetUpperHash()
{
// Check if upper hash is set (first byte not null)
if (g_UpperHash[100] == '\0') {
return GetMasterHash();
}
return std::string(g_UpperHash + 100, 64);
}
std::string GetPwdHash()
{
return std::string(g_MasterID, 64);
}
const Validation * GetValidation(int offset)
{
return (Validation*)(g_MasterID + offset);
}
std::string GetMasterId()
{
auto id = std::string(g_MasterID).substr(0, 16);
return id;
}
std::string GetHMAC(int offset)
{
const Validation * v= (Validation*)(g_MasterID + offset);
std::string hmac(v->Checksum, 16);
if (hmac.c_str()[0] == 0)
hmac = THIS_CFG.GetStr("settings", "HMAC");
return hmac;
}
void SetHMAC(const std::string str, int offset)
{
Validation* v = (Validation*)(g_MasterID + offset);
std::string hmac(v->Checksum, 16);
if (hmac.c_str()[0] == 0) {
memcpy(v->Checksum, str.c_str(), min(16, str.length()));
THIS_CFG.SetStr("settings", "HMAC", str);
}
}
extern "C" void shrink64to32(const char* input64, char* output32); // output32 必须至少 33 字节
extern "C" void shrink32to4(const char* input32, char* output4); // output4 必须至少 5 字节
// 获取授权信息存储路径
std::string GetLicensesPath()
{
std::string dbPath = GetDbPath();
// GetDbPath() 返回完整文件路径如 "C:\...\YAMA\YAMA.db"
// 需要去掉末尾文件名,保留目录部分
size_t pos = dbPath.find_last_of("\\/");
if (pos != std::string::npos) {
return dbPath.substr(0, pos + 1) + "licenses.ini";
}
return "licenses.ini";
}
// 保存授权信息到 INI 文件
bool SaveLicenseInfo(const std::string& deviceID, const std::string& passcode,
const std::string& hmac, const std::string& remark,
const std::string& authorization,
const std::string& frpConfig)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
// 以 DeviceID 为 section 名
cfg.SetStr(deviceID, "SerialNumber", deviceID);
cfg.SetStr(deviceID, "Passcode", passcode);
cfg.SetStr(deviceID, "HMAC", hmac);
cfg.SetStr(deviceID, "Remark", remark);
cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); // 默认状态为有效
// 保存 Authorization多层授权
// 注意authorization 参数传入时已经是混淆后的格式,直接保存
if (!authorization.empty()) {
cfg.SetStr(deviceID, "Authorization", authorization);
}
// 保存 FRP 配置(用于为下级提供 FRP 代理)
if (!frpConfig.empty()) {
cfg.SetStr(deviceID, "FrpConfig", frpConfig);
}
// 保存创建时间
SYSTEMTIME st;
GetLocalTime(&st);
char timeStr[32];
sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
cfg.SetStr(deviceID, "CreateTime", timeStr);
// 初始化扩展字段
cfg.SetStr(deviceID, "IP", "");
cfg.SetStr(deviceID, "Location", "");
cfg.SetStr(deviceID, "LastActiveTime", "");
return true;
}
// 加载授权信息
bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode,
std::string& hmac, std::string& remark)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
passcode = cfg.GetStr(deviceID, "Passcode", "");
if (passcode.empty())
return false;
hmac = cfg.GetStr(deviceID, "HMAC", "");
remark = cfg.GetStr(deviceID, "Remark", "");
return true;
}
// 加载授权的 FRP 配置
std::string LoadLicenseFrpConfig(const std::string& deviceID)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
return cfg.GetStr(deviceID, "FrpConfig", "");
}
// 加载授权的 Authorization用于 V2 授权返回给第一层)
std::string LoadLicenseAuthorization(const std::string& deviceID)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
return cfg.GetStr(deviceID, "Authorization", "");
}
// 更新授权的 AuthorizationV2 续期时更新)
bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& authorization)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
// 检查授权记录是否存在
std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", "");
if (existingPasscode.empty()) {
return false; // 授权记录不存在
}
cfg.SetStr(deviceID, "Authorization", authorization);
return true;
}
// IP 列表管理常量
#define MAX_IP_HISTORY 500 // 最多保留 几 个不同的 IP
// 解析 IP 列表字符串为 vector<pair<IP, 时间戳>>
// 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd)
static std::vector<std::pair<std::string, std::string>> ParseIPList(const std::string& ipListStr)
{
std::vector<std::pair<std::string, std::string>> result;
if (ipListStr.empty()) return result;
size_t start = 0;
while (start < ipListStr.length()) {
// 找到下一个逗号
size_t end = ipListStr.find(',', start);
if (end == std::string::npos) end = ipListStr.length();
// 提取单个 IP 条目
std::string entry = ipListStr.substr(start, end - start);
// 去除前后空格
size_t first = entry.find_first_not_of(' ');
size_t last = entry.find_last_not_of(' ');
if (first != std::string::npos && last != std::string::npos) {
entry = entry.substr(first, last - first + 1);
}
// 解析 IP|时间戳
size_t pipePos = entry.find('|');
if (pipePos != std::string::npos) {
std::string ip = entry.substr(0, pipePos);
std::string ts = entry.substr(pipePos + 1);
result.push_back({ ip, ts });
} else if (!entry.empty()) {
// 兼容旧格式(无时间戳)
result.push_back({ entry, "" });
}
start = end + 1;
}
return result;
}
// 将 IP 列表序列化为字符串
static std::string SerializeIPList(const std::vector<std::pair<std::string, std::string>>& ipList)
{
std::string result;
for (size_t i = 0; i < ipList.size(); ++i) {
if (i > 0) result += ", ";
result += ipList[i].first;
if (!ipList[i].second.empty()) {
result += "|";
result += ipList[i].second;
}
}
return result;
}
// 更新 IP 列表:添加新 IP 或更新已有 IP 的时间戳
// 格式: IP(机器名)|时间戳,例如 "1.2.3.4(PC01)|260219" (yyMMdd 格式)
// 返回更新后的 IP 列表字符串
static std::string UpdateIPList(const std::string& existingIPList, const std::string& newIP, const std::string& machineName = "")
{
if (newIP.empty()) return existingIPList;
// 构造 IP 标识:如果有机器名则为 "IP(机器名)",否则为 "IP"
std::string ipKey = newIP;
if (!machineName.empty()) {
// 机器名可能包含 "/"(如 "PC01/GroupName"),只取第一部分
std::string shortName = machineName;
size_t slashPos = machineName.find('/');
if (slashPos != std::string::npos) {
shortName = machineName.substr(0, slashPos);
}
ipKey = newIP + "(" + shortName + ")";
}
// 获取当前时间戳 (yyMMdd 格式6位)
SYSTEMTIME st;
GetLocalTime(&st);
char timestamp[8];
sprintf_s(timestamp, "%02d%02d%02d", st.wYear % 100, st.wMonth, st.wDay);
// 解析现有 IP 列表
auto ipList = ParseIPList(existingIPList);
// 提取纯 IP去掉括号内的机器名
auto extractIP = [](const std::string& s) -> std::string {
size_t pos = s.find('(');
return pos != std::string::npos ? s.substr(0, pos) : s;
};
// 查找是否已存在该 IP只比较 IP 部分,兼容旧格式)
bool found = false;
for (auto& entry : ipList) {
if (extractIP(entry.first) == newIP) {
entry.first = ipKey; // 更新为新格式(带机器名)
entry.second = timestamp; // 更新时间戳
found = true;
break;
}
}
if (!found) {
// 新 IP添加到开头最近的在前
ipList.insert(ipList.begin(), { ipKey, timestamp });
// 限制数量
if (ipList.size() > MAX_IP_HISTORY) {
ipList.resize(MAX_IP_HISTORY);
}
}
return SerializeIPList(ipList);
}
// 获取 IP 列表中的 IP 数量
static int GetIPCount(const std::string& ipListStr)
{
if (ipListStr.empty()) return 0;
auto ipList = ParseIPList(ipListStr);
return (int)ipList.size();
}
// 更新授权活跃信息
bool UpdateLicenseActivity(const std::string& deviceID, const std::string& passcode,
const std::string& hmac, const std::string& ip,
const std::string& location, const std::string& machineName)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
// 检查该授权是否存在
std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", "");
if (existingPasscode.empty()) {
// 授权不存在,但验证成功了,说明是既往授权,自动添加记录
cfg.SetStr(deviceID, "SerialNumber", deviceID);
cfg.SetStr(deviceID, "Passcode", passcode);
cfg.SetStr(deviceID, "HMAC", hmac);
cfg.SetStr(deviceID, "Remark", "既往授权自动加入");
cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); // 新记录默认为有效
} else {
// 授权已存在,更新 passcode续期后 passcode 会变化)
cfg.SetStr(deviceID, "Passcode", passcode);
cfg.SetStr(deviceID, "HMAC", hmac);
}
// 更新最后活跃时间
SYSTEMTIME st;
GetLocalTime(&st);
char timeStr[32];
sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
cfg.SetStr(deviceID, "LastActiveTime", timeStr);
// 如果是新添加的记录,设置创建时间
if (existingPasscode.empty()) {
cfg.SetStr(deviceID, "CreateTime", timeStr);
}
// 更新 IP 列表(追加新 IP 或更新已有 IP 的时间戳)
// 格式: IP(机器名)|yyMMdd
if (!ip.empty()) {
std::string existingIPList = cfg.GetStr(deviceID, "IP", "");
std::string newIPList = UpdateIPList(existingIPList, ip, machineName);
cfg.SetStr(deviceID, "IP", newIPList);
}
if (!location.empty()) {
cfg.SetStr(deviceID, "Location", location);
}
return true;
}
// 检查授权是否已被撤销
bool IsLicenseRevoked(const std::string& deviceID)
{
std::string iniPath = GetLicensesPath();
config cfg(iniPath);
std::string status = cfg.GetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE);
return (status == LICENSE_STATUS_REVOKED);
}
#ifdef _WIN64
#pragma comment(lib, "lib/shrink_x64.lib")
#else
#pragma comment(lib, "lib/shrink.lib")
#endif
std::string GetFinderString(const char* buf)
{
char output32[100] = {};
memcpy(output32, buf, 64);
if (GetPwdHash() == GetMasterHash()) {
return std::string(output32, 100);
}
shrink64to32(buf, output32+64);
return std::string(output32, 96);
}
bool WritePwdHash(char* target, const std::string & pwdHash, const Validation& verify)
{
char output32[33], output4[5];
shrink64to32(pwdHash.c_str(), output32);
shrink32to4(output32, output4);
if (output32[0] == 0 || output4[0] == 0)
return false;
memcpy(target, pwdHash.c_str(), pwdHash.length());
memcpy(target + 64, output32, 32);
memcpy(target + 96, output4, 4);
#ifdef _DEBUG
ASSERT(IsPwdHashValid(target));
#endif
memcpy(target+100, &verify, sizeof(verify));
return true;
}
bool IsPwdHashValid(const char* hash)
{
const char* ptr = hash ? hash : g_MasterID;
if (ptr == GetMasterHash())
return true;
std::string pwdHash(ptr, 64), s1(ptr +64, 32), s2(ptr +96, 4);
char output32[33], output4[5];
shrink64to32(pwdHash.c_str(), output32);
shrink32to4(output32, output4);
if (memcmp(output32, s1.c_str(), 32) || memcmp(output4, s2.c_str(), 4)) {
// 哈希校验失败,尝试离线校验 Authorization
std::string authObf = THIS_CFG.GetStr("settings", "Authorization", "");
if (!authObf.empty() && TcpClient::IsAuthorizationValid(authObf)) {
return true; // Authorization 签名有效
}
return false;
}
return true;
}
CPasswordDlg::CPasswordDlg(CWnd* pParent /*=nullptr*/)
: CDialogLangEx(IDD_DIALOG_PASSWORD, pParent)
, m_sDeviceID(_T(""))
, m_sPassword(_T(""))
, m_sPasscodeHmac(THIS_CFG.GetStr("settings", "PwdHmac", "").c_str())
, m_nBindType(THIS_CFG.GetInt("settings", "BindType", 0))
{
m_hIcon = nullptr;
}
CPasswordDlg::~CPasswordDlg()
{
}
void CPasswordDlg::DoDataExchange(CDataExchange* pDX)
{
__super::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_DEVICEID, m_EditDeviceID);
DDX_Control(pDX, IDC_EDIT_DEVICEPWD, m_EditPassword);
DDX_Text(pDX, IDC_EDIT_DEVICEID, m_sDeviceID);
DDV_MaxChars(pDX, m_sDeviceID, 19);
DDX_Text(pDX, IDC_EDIT_DEVICEPWD, m_sPassword);
DDV_MaxChars(pDX, m_sPassword, 42);
DDX_Control(pDX, IDC_COMBO_BIND, m_ComboBinding);
DDX_Control(pDX, IDC_EDIT_PASSCODE_HMAC, m_EditPasscodeHmac);
DDX_Text(pDX, IDC_EDIT_PASSCODE_HMAC, m_sPasscodeHmac);
DDX_Control(pDX, IDC_EDIT_ROOT_CERT, m_EditRootCert);
DDX_Text(pDX, IDC_EDIT_ROOT_CERT, m_sRootCert);
DDX_CBIndex(pDX, IDC_COMBO_BIND, m_nBindType);
}
BEGIN_MESSAGE_MAP(CPasswordDlg, CDialogEx)
ON_CBN_SELCHANGE(IDC_COMBO_BIND, &CPasswordDlg::OnCbnSelchangeComboBind)
END_MESSAGE_MAP()
BOOL CPasswordDlg::OnInitDialog()
{
__super::OnInitDialog();
// 多语言翻译 - Static控件
SetDlgItemText(IDC_STATIC_PASSWORD_SERIAL, _TR("序 列 号:"));
SetDlgItemText(IDC_STATIC_PASSWORD_TOKEN, _TR("授权口令:"));
SetDlgItemText(IDC_STATIC_PASSWORD_METHOD, _TR("授权方式:"));
SetDlgItemText(IDC_STATIC_PASSWORD_VERIFY, _TR("验 证 码:"));
SetDlgItemText(IDC_STATIC_ROOT_CERT, _TR("根 凭 证:"));
// 设置对话框标题和控件文本(解决英语系统乱码问题)
SetWindowText(_TR("口令"));
SetDlgItemText(IDOK, _TR("确定"));
SetDlgItemText(IDCANCEL, _TR("取消"));
// TODO: 在此添加额外的初始化
m_hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_PASSWORD));
SetIcon(m_hIcon, FALSE);
m_ComboBinding.InsertStringL(0, "计算机硬件信息");
m_ComboBinding.InsertStringL(1, "主控IP或域名信息");
m_ComboBinding.SetCurSel(m_nBindType);
// 加载已保存的根凭证
std::string savedAuth = THIS_CFG.GetStr("settings", "Authorization", "");
if (!savedAuth.empty()) {
m_sRootCert = savedAuth.c_str();
m_EditRootCert.SetWindowText(m_sRootCert);
}
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
void CPasswordDlg::OnCbnSelchangeComboBind()
{
m_nBindType = m_ComboBinding.GetCurSel();
std::string hardwareID = CMy2015RemoteDlg::GetHardwareID(m_nBindType);
m_sDeviceID = getFixedLengthID(hashSHA256(hardwareID)).c_str();
m_EditDeviceID.SetWindowTextA(m_sDeviceID);
auto master = THIS_CFG.GetStr("settings", "master", "");
if (m_nBindType == 1) {
MessageBoxL(_L("请确认是否正确设置公网地址IP或域名\r\n"
"绑定IP后主控只能使用指定IP绑定域名后\r\n"
"主控只能使用指定域名。当前公网地址: \r\n")+
+ CString(master.empty() ? _L("未设置") : master.c_str()), "提示", MB_OK | MB_ICONWARNING);
}
}
void CPasswordDlg::OnOK()
{
UpdateData(TRUE);
if (!m_sDeviceID.IsEmpty()) {
THIS_CFG.SetInt("settings", "BindType", m_nBindType);
THIS_CFG.SetStr("settings", "SN", m_sDeviceID.GetString()); // 切换绑定方式时同步保存 SN
THIS_CFG.SetStr("settings", "PwdHmac", m_sPasscodeHmac.GetString());
// 保存根凭证(可选,用于多层授权)
if (!m_sRootCert.IsEmpty()) {
THIS_CFG.SetStr("settings", "Authorization", m_sRootCert.GetString());
}
}
__super::OnOK();
}
// CPasswordDlg 消息处理程序
IMPLEMENT_DYNAMIC(CPwdGenDlg, CDialogEx)
CPwdGenDlg::CPwdGenDlg(CWnd* pParent /*=nullptr*/)
: CDialogLangEx(IDD_DIALOG_KEYGEN, pParent)
, m_sDeviceID(_T(""))
, m_sPassword(_T(""))
, m_sUserPwd(_T(""))
, m_sHMAC(_T(""))
, m_ExpireTm(COleDateTime::GetCurrentTime())
, m_StartTm(COleDateTime::GetCurrentTime())
, m_nHostNum(2)
, m_nAuthHostNum(2) // 默认与连接数相同
, m_bAuthHostNumManual(FALSE) // 未手动修改
, m_bIsLocalDevice(FALSE)
, m_nVersion(0)
, m_sPrivateKeyPath(_T(""))
, m_nFrpRemotePort(0)
{
}
CPwdGenDlg::~CPwdGenDlg()
{
}
void CPwdGenDlg::DoDataExchange(CDataExchange* pDX)
{
__super::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_DEVICEID, m_EditDeviceID);
DDX_Control(pDX, IDC_EDIT_DEVICEPWD, m_EditPassword);
DDX_Control(pDX, IDC_EDIT_USERPWD, m_EditUserPwd);
DDX_Text(pDX, IDC_EDIT_DEVICEID, m_sDeviceID);
DDV_MaxChars(pDX, m_sDeviceID, 19);
DDX_Text(pDX, IDC_EDIT_DEVICEPWD, m_sPassword);
DDV_MaxChars(pDX, m_sPassword, 42);
DDX_Text(pDX, IDC_EDIT_USERPWD, m_sUserPwd);
DDV_MaxChars(pDX, m_sUserPwd, 24);
DDX_Control(pDX, IDC_EXPIRE_DATE, m_PwdExpireDate);
DDX_DateTimeCtrl(pDX, IDC_EXPIRE_DATE, m_ExpireTm);
DDX_Control(pDX, IDC_START_DATE, m_StartDate);
DDX_DateTimeCtrl(pDX, IDC_START_DATE, m_StartTm);
DDX_Control(pDX, IDC_EDIT_HOSTNUM, m_EditHostNum);
DDX_Text(pDX, IDC_EDIT_HOSTNUM, m_nHostNum);
DDV_MinMaxInt(pDX, m_nHostNum, 2, 10000);
DDX_Control(pDX, IDC_EDIT_HMAC, m_EditHMAC);
DDX_Control(pDX, IDC_EDIT_AUTHORIZATION, m_EditAuthorization);
DDX_Control(pDX, IDC_BUTTON_SAVE_LICENSE, m_BtnSaveLicense);
DDX_Text(pDX, IDC_EDIT_HMAC, m_sHMAC);
DDX_Text(pDX, IDC_EDIT_AUTHORIZATION, m_sAuthorization);
// V2 Authorization 下级并发数
DDX_Control(pDX, IDC_EDIT_AUTH_HOSTNUM, m_EditAuthHostNum);
DDX_Text(pDX, IDC_EDIT_AUTH_HOSTNUM, m_nAuthHostNum);
DDV_MinMaxInt(pDX, m_nAuthHostNum, 2, 10000);
DDX_Control(pDX, IDC_COMBO_VERSION, m_ComboVersion);
DDX_Control(pDX, IDC_EDIT_PRIVATEKEY, m_EditPrivateKey);
DDX_Control(pDX, IDC_BUTTON_BROWSE_KEY, m_BtnBrowseKey);
DDX_Control(pDX, IDC_BUTTON_GEN_KEYPAIR, m_BtnGenKeyPair);
DDX_CBIndex(pDX, IDC_COMBO_VERSION, m_nVersion);
DDX_Text(pDX, IDC_EDIT_PRIVATEKEY, m_sPrivateKeyPath);
// FRP 控件
DDX_Control(pDX, IDC_CHECK_FRP_PROXY, m_CheckFrpProxy);
DDX_Control(pDX, IDC_EDIT_FRP_REMOTE_PORT, m_EditFrpRemotePort);
DDX_Control(pDX, IDC_BTN_FRP_AUTO_PORT, m_BtnFrpAutoPort);
DDX_Control(pDX, IDC_STATIC_FRP_INFO, m_StaticFrpInfo);
DDX_Text(pDX, IDC_EDIT_FRP_REMOTE_PORT, m_nFrpRemotePort);
}
BEGIN_MESSAGE_MAP(CPwdGenDlg, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON_GENKEY, &CPwdGenDlg::OnBnClickedButtonGenkey)
ON_BN_CLICKED(IDC_BUTTON_SAVE_LICENSE, &CPwdGenDlg::OnBnClickedButtonSaveLicense)
ON_CBN_SELCHANGE(IDC_COMBO_VERSION, &CPwdGenDlg::OnCbnSelchangeComboVersion)
ON_BN_CLICKED(IDC_BUTTON_BROWSE_KEY, &CPwdGenDlg::OnBnClickedButtonBrowseKey)
ON_BN_CLICKED(IDC_BUTTON_GEN_KEYPAIR, &CPwdGenDlg::OnBnClickedButtonGenKeypair)
ON_BN_CLICKED(IDC_CHECK_FRP_PROXY, &CPwdGenDlg::OnBnClickedCheckFrpProxy)
ON_BN_CLICKED(IDC_BTN_FRP_AUTO_PORT, &CPwdGenDlg::OnBnClickedBtnFrpAutoPort)
ON_EN_CHANGE(IDC_EDIT_HOSTNUM, &CPwdGenDlg::OnEnChangeEditHostNum)
ON_EN_CHANGE(IDC_EDIT_AUTH_HOSTNUM, &CPwdGenDlg::OnEnChangeEditAuthHostNum)
END_MESSAGE_MAP()
// 构建 V1 Authorization第一层/下级返回给下级)
std::string BuildV1Authorization(const std::string& sn, bool heartbeat) {
std::string storedAuthObf = THIS_CFG.GetStr("settings", "Authorization", "");
std::string storedAuth = TcpClient::DeobfuscateAuthorization(storedAuthObf);
std::string masterIP;
int masterPort;
CMy2015RemoteDlg::GetEffectiveMasterAddress(masterIP, masterPort, heartbeat);
std::string hmacServer = masterIP.empty() ? "" : masterIP + ":" + std::to_string(masterPort);
if (storedAuth.empty() || hmacServer.empty()) {
return "";
}
std::string authStr;
auto authParts = splitString(storedAuth, '|');
if (authParts.size() == 5) {
// V2 格式5段第一层给第二层
// 验证 snHashPrefix 匹配(防止使用其他第一层的 Authorization
std::string storedPrefix = authParts[3];
std::string mySN = THIS_CFG.GetStr("settings", "SN", "");
std::string expectedPrefix = computeSnHashPrefix(mySN);
if (storedPrefix != expectedPrefix) {
Mprintf("V1 Authorization snHashPrefix 不匹配: 期望 %s, 存储 %s\n",
expectedPrefix.c_str(), storedPrefix.c_str());
return "";
}
// 组装完整 AuthorizationV16段
authStr = authParts[0] + "|" + authParts[1] + "|" + authParts[2] + "|" + authParts[3] + "|" + hmacServer + "|" + authParts[4];
if (!heartbeat)
Mprintf("V1 返回 Authorization: %s. AuthServer: %s\n", sn.c_str(), hmacServer.c_str());
} else if (authParts.size() == 6) {
// V1 格式6段第二层及以下给下级
// 替换 hmacServer 为自己的地址
authStr = authParts[0] + "|" + authParts[1] + "|" + authParts[2] + "|" + authParts[3] + "|" + hmacServer + "|" + authParts[5];
if (!heartbeat)
Mprintf("V1 转发 Authorization: %s. AuthServer: %s\n", sn.c_str(), hmacServer.c_str());
}
return authStr.empty() ? "" : TcpClient::ObfuscateAuthorization(authStr);
}
void CPwdGenDlg::OnBnClickedButtonGenkey()
{
UpdateData(TRUE);
CString strBeginDate = m_StartTm.FormatL("%Y%m%d");
CString strEndDate = m_ExpireTm.FormatL("%Y%m%d");
CString hostNum;
hostNum.FormatL("%04d", m_nHostNum);
std::string fixedKey;
std::string pwdHmacStr; // V1: 数字字符串, V2: "v2:..." 签名
std::string hmacV1; // 只有 V1 使用
if (m_nVersion == 0) {
// V1 (HMAC) 模式
if (m_sUserPwd.IsEmpty()) return;
std::string pwdHash = hashSHA256(m_sUserPwd.GetString());
if (pwdHash != GetPwdHash()) {
Mprintf("hashSHA256 [%s]: %s\n", m_sUserPwd, pwdHash.c_str());
MessageBoxL("您输入的密码不正确,无法生成口令!", "提示", MB_OK | MB_ICONWARNING);
m_sUserPwd.Empty();
return;
}
// 密码形式20250209 - 20350209: SHA256: HostNum
std::string password = std::string(strBeginDate.GetString()) + " - " + strEndDate.GetBuffer() + ": " + GetPwdHash() + ": " + hostNum.GetBuffer();
std::string finalKey = deriveKey(password, m_sDeviceID.GetString());
fixedKey = strBeginDate.GetString() + std::string("-") + strEndDate.GetBuffer() + std::string("-") + hostNum.GetString() + "-" +
getFixedLengthID(finalKey);
m_sPassword = fixedKey.c_str();
m_EditPassword.SetWindowTextA(fixedKey.c_str());
hmacV1 = genHMAC(pwdHash, m_sUserPwd.GetString());
uint64_t pwdHmac = SignMessage(m_sUserPwd.GetString(), (BYTE*)fixedKey.c_str(), fixedKey.length());
pwdHmacStr = std::to_string(pwdHmac);
m_sHMAC = pwdHmacStr.c_str();
m_EditHMAC.SetWindowTextA(m_sHMAC);
// 多层授权:检查是否有 Authorization + master公网地址
std::string fullAuth = BuildV1Authorization(m_sDeviceID.GetString());
if (!fullAuth.empty()) {
m_sAuthorization = fullAuth.c_str();
m_EditAuthorization.SetWindowText(m_sAuthorization);
}
else {
m_sAuthorization.Empty();
m_EditAuthorization.SetWindowText(_T(""));
}
} else {
// V2 (ECDSA) 模式
if (m_sPrivateKeyPath.IsEmpty()) {
MessageBoxL("请选择私钥文件!", "提示", MB_OK | MB_ICONWARNING);
return;
}
// 检查私钥文件是否存在
if (GetFileAttributes(m_sPrivateKeyPath) == INVALID_FILE_ATTRIBUTES) {
MessageBoxL("私钥文件不存在!", "错误", MB_OK | MB_ICONERROR);
return;
}
// V2 口令格式与 V1 相同,但不需要密码验证
// 使用 deviceId + dates 作为种子生成 key
std::string seed = m_sDeviceID.GetString() + std::string(strBeginDate.GetString()) +
strEndDate.GetString() + hostNum.GetString();
std::string seedHash = hashSHA256(seed);
fixedKey = strBeginDate.GetString() + std::string("-") + strEndDate.GetBuffer() +
std::string("-") + hostNum.GetString() + "-" + getFixedLengthID(seedHash);
// 使用私钥签名
pwdHmacStr = signPasswordV2(m_sDeviceID.GetString(), fixedKey, m_sPrivateKeyPath.GetString());
if (pwdHmacStr.empty()) {
MessageBoxL("生成 V2 签名失败!\n请检查私钥文件是否有效。", "错误", MB_OK | MB_ICONERROR);
return;
}
m_sPassword = fixedKey.c_str();
m_EditPassword.SetWindowTextA(fixedKey.c_str());
m_sHMAC = pwdHmacStr.c_str();
m_EditHMAC.SetWindowTextA(pwdHmacStr.c_str());
// V2 模式:生成 Authorization使用 deviceID 计算 snHashPrefix
// Authorization 的并发数限制:使用下级并发数(已自动同步或手动设置)
CString authHostNum;
authHostNum.Format(_T("%04d"), m_nAuthHostNum);
// 从 fixedKey 提取 license: "20260317-20270317-0256-..." → "20260317|20270317|0256"
std::string license = strBeginDate.GetString() + std::string("|") +
strEndDate.GetString() + "|" + authHostNum.GetString();
std::string snHashPrefix = computeSnHashPrefix(m_sDeviceID.GetString());
std::string authSig = signAuthorizationV2(license, snHashPrefix, m_sPrivateKeyPath.GetString());
if (!authSig.empty()) {
// Authorization 格式V25段: startDate|endDate|hostNum|snHashPrefix|signature
std::string auth = license + "|" + snHashPrefix + "|" + authSig;
m_sAuthorization = TcpClient::ObfuscateAuthorization(auth).c_str(); // 混淆后显示
m_EditAuthorization.SetWindowText(m_sAuthorization);
GetDlgItem(IDC_STATIC_AUTHORIZATION)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_EDIT_AUTHORIZATION)->ShowWindow(SW_SHOW);
Mprintf("V2 生成 Authorization: %s (snHashPrefix=%s, authHostNum=%s)\n",
m_sDeviceID.GetString(), snHashPrefix.c_str(), authHostNum.GetString());
} else {
m_sAuthorization.Empty();
m_EditAuthorization.SetWindowText(_T(""));
}
}
// 公共部分:判断是否为本机授权
std::string hardwareID = CMy2015RemoteDlg::GetHardwareID();
std::string hashedID = hashSHA256(hardwareID);
std::string deviceID = getFixedLengthID(hashedID);
m_bIsLocalDevice = (deviceID == m_sDeviceID.GetString());
bool isSuperAdmin = GetUpperHash()==GetPwdHash();
if (m_bIsLocalDevice && isSuperAdmin) {
// 给自己授权,自动保存
auto settings = "settings", pwdKey = "Password";
THIS_CFG.SetStr(settings, pwdKey, fixedKey.c_str());
THIS_CFG.SetStr("settings", "SN", deviceID);
THIS_CFG.SetStr(settings, "HMAC", hmacV1);
THIS_CFG.SetStr(settings, "PwdHmac", pwdHmacStr);
m_BtnSaveLicense.EnableWindow(FALSE);
} else if (m_bIsLocalDevice && !isSuperAdmin) {
// 没有权限给自己生成授权
MessageBoxL("您无法给自己授权!\n生成的授权信息仅供参考,不会自动保存。",
"提示", MB_OK | MB_ICONWARNING);
m_BtnSaveLicense.EnableWindow(FALSE);
} else {
m_BtnSaveLicense.EnableWindow(TRUE);
}
}
BOOL CPwdGenDlg::OnInitDialog()
{
__super::OnInitDialog();
// 设置对话框标题和控件文本(解决英语系统乱码问题)
SetWindowText(_TR("生成口令"));
SetDlgItemText(IDC_BUTTON_SAVE_LICENSE, _TR("保存授权"));
SetDlgItemText(IDCANCEL, _TR("取消"));
SetDlgItemText(IDC_STATIC_PWD_LABEL, _TR("密 码:"));
SetDlgItemText(IDC_STATIC_KEYGEN_VERSION, _TR("版 本:"));
SetDlgItemText(IDC_STATIC_KEYGEN_SERIAL, _TR("序列号:"));
SetDlgItemText(IDC_STATIC_KEYGEN_EXPIRE, _TR("有效期:"));
SetDlgItemText(IDC_STATIC_KEYGEN_CONN, _TR("连接数:"));
SetDlgItemText(IDC_STATIC_AUTH_HOSTNUM, _TR("下级连接数:"));
SetDlgItemText(IDC_STATIC_KEYGEN_TOKEN, _TR("口 令:"));
SetDlgItemText(IDC_STATIC_KEYGEN_HMAC_2373, _TR("HMAC:"));
SetDlgItemText(IDC_BUTTON_GENKEY, _TR("生成"));
GetDlgItem(IDC_STATIC_KEYGEN_VERSION)->EnableWindow(GetUpperHash() == GetPwdHash());
// TODO: 在此添加额外的初始化
m_hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_PASSWORD));
SetIcon(m_hIcon, FALSE);
// 初始化版本下拉框
m_ComboVersion.InsertString(0, _T("V1 (HMAC)"));
m_ComboVersion.InsertString(1, _T("V2 (ECDSA)"));
m_ComboVersion.SetCurSel(0);
m_ComboVersion.EnableWindow(GetUpperHash() == GetPwdHash());
m_nVersion = 0;
// 初始状态V1 模式,隐藏私钥控件和下级并发数
m_EditPrivateKey.ShowWindow(SW_HIDE);
m_BtnBrowseKey.ShowWindow(SW_HIDE);
m_BtnGenKeyPair.ShowWindow(SW_HIDE);
m_EditUserPwd.ShowWindow(SW_SHOW);
// 隐藏下级并发数控件(仅 V2 模式使用)
GetDlgItem(IDC_STATIC_AUTH_HOSTNUM)->ShowWindow(SW_HIDE);
m_EditAuthHostNum.ShowWindow(SW_HIDE);
// 初始状态禁用保存按钮
m_BtnSaveLicense.EnableWindow(FALSE);
// 加载已配置的 V2 私钥路径
std::string v2Key = THIS_CFG.GetStr("settings", "V2PrivateKey", "");
if (!v2Key.empty()) {
m_sPrivateKeyPath = v2Key.c_str();
m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath);
}
// 设置根凭证标签翻译
SetDlgItemText(IDC_STATIC_AUTHORIZATION, _TR("根凭证:"));
// 检查并显示当前服务端的 Authorization
std::string storedAuth = THIS_CFG.GetStr("settings", "Authorization", "");
if (!storedAuth.empty()) {
// 服务端已获得上级授权,在标题栏提示
CString title;
title.Format(_T("%s - %s"), _TR("生成口令"), _TR("已获得上级授权"));
SetWindowText(title);
}
// 初始化 FRP 代理控件
SetDlgItemText(IDC_CHECK_FRP_PROXY, _TR("提供 FRP 代理"));
SetDlgItemText(IDC_STATIC_FRP_REMOTE_PORT, _TR("远程端口:"));
SetDlgItemText(IDC_BTN_FRP_AUTO_PORT, _TR("自动"));
m_nFrpRemotePort = 0;
m_sFrpConfig.clear();
// 检查 FRPS 是否已配置
if (CFrpsForSubDlg::IsFrpsConfigured()) {
FrpsConfig frpsConfig = CFrpsForSubDlg::GetFrpsConfig();
CString frpsInfo;
frpsInfo.Format(_T("FRPS: %s:%d"), CString(frpsConfig.server.c_str()), frpsConfig.port);
m_StaticFrpInfo.SetWindowText(frpsInfo);
m_CheckFrpProxy.EnableWindow(TRUE);
} else {
m_StaticFrpInfo.SetWindowText(_TR("(未配置 FRPS请先通过 扩展→下级FRP代理设置)"));
m_CheckFrpProxy.EnableWindow(FALSE);
}
m_CheckFrpProxy.SetCheck(BST_UNCHECKED);
UpdateFrpControlStates();
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
void CPwdGenDlg::OnCbnSelchangeComboVersion()
{
m_nVersion = m_ComboVersion.GetCurSel();
// 切换版本时清理已生成的值
m_EditPassword.SetWindowText(_T(""));
m_EditHMAC.SetWindowText(_T(""));
m_EditAuthorization.SetWindowText(_T(""));
m_sPassword.Empty();
m_sHMAC.Empty();
m_sAuthorization.Empty();
m_BtnSaveLicense.EnableWindow(FALSE);
// 重置下级并发数手动标志,并同步当前连接数
m_bAuthHostNumManual = FALSE;
CString strHostNum;
m_EditHostNum.GetWindowText(strHostNum);
int hostNum = _ttoi(strHostNum);
if (hostNum >= 2) {
m_nAuthHostNum = hostNum;
CString strAuthHostNum;
strAuthHostNum.Format(_T("%d"), m_nAuthHostNum);
m_EditAuthHostNum.SetWindowText(strAuthHostNum);
}
// 获取"密码"标签控件和"授权"控件
CWnd* pLabel = GetDlgItem(IDC_STATIC_PWD_LABEL);
CWnd* pAuthLabel = GetDlgItem(IDC_STATIC_AUTHORIZATION);
CWnd* pAuthHostNumLabel = GetDlgItem(IDC_STATIC_AUTH_HOSTNUM);
if (m_nVersion == 0) {
// V1 (HMAC) 模式:显示密码输入框,隐藏私钥相关控件
m_EditUserPwd.ShowWindow(SW_SHOW);
m_EditPrivateKey.ShowWindow(SW_HIDE);
m_BtnBrowseKey.ShowWindow(SW_HIDE);
m_BtnGenKeyPair.ShowWindow(SW_HIDE);
if (pLabel) pLabel->SetWindowText(_TR("密 码:"));
// V1 模式隐藏下级并发数
if (pAuthHostNumLabel) pAuthHostNumLabel->ShowWindow(SW_HIDE);
m_EditAuthHostNum.ShowWindow(SW_HIDE);
} else {
// V2 (ECDSA) 模式:隐藏密码输入框,显示私钥相关控件
m_EditUserPwd.ShowWindow(SW_HIDE);
m_EditPrivateKey.ShowWindow(SW_SHOW);
m_BtnBrowseKey.ShowWindow(SW_SHOW);
m_BtnGenKeyPair.ShowWindow(SW_SHOW);
if (pLabel) pLabel->SetWindowText(_TR("私 钥:"));
// V2 模式显示下级并发数
if (pAuthHostNumLabel) pAuthHostNumLabel->ShowWindow(SW_SHOW);
m_EditAuthHostNum.ShowWindow(SW_SHOW);
// 如果已有有效私钥,禁用浏览和生成按钮,私钥路径只读
std::string v2Key = THIS_CFG.GetStr("settings", "V2PrivateKey", "");
bool hasValidKey = !v2Key.empty() && GetFileAttributes(v2Key.c_str()) != INVALID_FILE_ATTRIBUTES;
m_BtnBrowseKey.EnableWindow(!hasValidKey);
m_BtnGenKeyPair.EnableWindow(!hasValidKey);
m_EditPrivateKey.SetReadOnly(hasValidKey);
}
}
void CPwdGenDlg::OnEnChangeEditHostNum()
{
// 如果下级并发数未手动修改,则跟随连接数变化
if (!m_bAuthHostNumManual && m_nVersion == 1) { // 仅 V2 模式需要同步
CString strHostNum;
m_EditHostNum.GetWindowText(strHostNum);
int hostNum = _ttoi(strHostNum);
if (hostNum >= 2) {
m_nAuthHostNum = hostNum;
CString strAuthHostNum;
strAuthHostNum.Format(_T("%d"), m_nAuthHostNum);
m_EditAuthHostNum.SetWindowText(strAuthHostNum);
}
}
}
void CPwdGenDlg::OnEnChangeEditAuthHostNum()
{
// 检测是否为用户手动修改(而非程序同步)
// 通过比较当前值与连接数来判断
CString strAuthHostNum;
m_EditAuthHostNum.GetWindowText(strAuthHostNum);
CString strHostNum;
m_EditHostNum.GetWindowText(strHostNum);
// 如果两个值不同,说明用户手动修改了
if (strAuthHostNum != strHostNum && !strAuthHostNum.IsEmpty()) {
m_bAuthHostNumManual = TRUE;
}
}
void CPwdGenDlg::OnBnClickedButtonBrowseKey()
{
// 检查是否有权使用 V2 私钥
if (GetUpperHash() != GetPwdHash()) {
MessageBoxL("您无法使用 V2 私钥签名!", "提示", MB_OK | MB_ICONWARNING);
return;
}
CFileDialog dlg(TRUE, _T("key"), nullptr,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
_T("Key Files (*.key;*.pem)|*.key;*.pem|All Files (*.*)|*.*||"),
this);
dlg.m_ofn.lpstrTitle = _T("Select Private Key");
if (dlg.DoModal() == IDOK) {
m_sPrivateKeyPath = dlg.GetPathName();
m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath);
}
}
void CPwdGenDlg::OnBnClickedButtonGenKeypair()
{
// 检查是否有权生成 V2 密钥对
if (GetUpperHash() != GetPwdHash()) {
MessageBoxL("您无法生成 V2 密钥对!", "提示", MB_OK | MB_ICONWARNING);
return;
}
// 选择私钥保存位置
CFileDialog dlg(FALSE, _T("key"), _T("private_key.key"),
OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY,
_T("Key Files (*.key)|*.key|All Files (*.*)|*.*||"),
this);
dlg.m_ofn.lpstrTitle = _T("Save Private Key");
if (dlg.DoModal() != IDOK) {
return;
}
CString privateKeyPath = dlg.GetPathName();
// 生成密钥对
BYTE publicKey[V2_PUBKEY_SIZE];
if (!GenerateKeyPairV2(privateKeyPath.GetString(), publicKey)) {
MessageBoxL("生成密钥对失败!", "错误", MB_OK | MB_ICONERROR);
return;
}
// 更新私钥路径(仅更新界面,不保存到配置,需通过菜单设置)
m_sPrivateKeyPath = privateKeyPath;
m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath);
// 格式化公钥为 C 代码
std::string pubKeyCode = formatPublicKeyAsCode(publicKey);
// 生成公钥文件路径(与私钥同目录,扩展名改为 .pub.h
CString publicKeyPath = privateKeyPath;
int dotPos = publicKeyPath.ReverseFind('.');
if (dotPos > 0) {
publicKeyPath = publicKeyPath.Left(dotPos) + _T(".pub.h");
} else {
publicKeyPath += _T(".pub.h");
}
// 保存公钥到文件
FILE* fp = nullptr;
if (fopen_s(&fp, publicKeyPath.GetString(), "w") == 0 && fp) {
fprintf(fp, "// V2 License Public Key\n");
fprintf(fp, "// Generated: %s\n\n", CTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S").GetString());
fprintf(fp, "%s\n", pubKeyCode.c_str());
fclose(fp);
}
// 复制公钥到剪贴板
if (OpenClipboard()) {
EmptyClipboard();
size_t len = pubKeyCode.length() + 1;
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
if (hMem) {
char* pMem = (char*)GlobalLock(hMem);
if (pMem) {
memcpy(pMem, pubKeyCode.c_str(), len);
GlobalUnlock(hMem);
SetClipboardData(CF_TEXT, hMem);
}
}
CloseClipboard();
}
// 简短提示
CString msg;
msg.Format(_T("密钥对生成成功!\n\n私钥: %s\n公钥: %s\n\n公钥代码已复制到剪贴板"),
privateKeyPath.GetString(), publicKeyPath.GetString());
MessageBox(msg, _TR("成功"), MB_OK | MB_ICONINFORMATION);
}
void CPwdGenDlg::OnBnClickedCheckFrpProxy()
{
UpdateFrpControlStates();
// 勾选时,如果端口为 0自动填入下一个可用端口
if (m_CheckFrpProxy.GetCheck() == BST_CHECKED) {
CString portText;
m_EditFrpRemotePort.GetWindowText(portText);
int currentPort = _ttoi(portText);
if (currentPort <= 0) {
int port = CFrpsForSubDlg::FindNextAvailablePort();
if (port > 0) {
CString portStr;
portStr.Format(_T("%d"), port);
m_EditFrpRemotePort.SetWindowText(portStr);
}
}
}
}
void CPwdGenDlg::OnBnClickedBtnFrpAutoPort()
{
// 自动查找下一个可用端口(不保存,等保存授权时再记录)
int port = CFrpsForSubDlg::FindNextAvailablePort();
if (port > 0) {
m_nFrpRemotePort = port;
CString portStr;
portStr.Format(_T("%d"), port);
m_EditFrpRemotePort.SetWindowText(portStr);
} else {
MessageBoxL("端口范围已满,无法自动分配!", "提示", MB_OK | MB_ICONWARNING);
}
}
void CPwdGenDlg::UpdateFrpControlStates()
{
BOOL frpEnabled = (m_CheckFrpProxy.GetCheck() == BST_CHECKED);
BOOL frpsConfigured = CFrpsForSubDlg::IsFrpsConfigured();
m_EditFrpRemotePort.EnableWindow(frpEnabled && frpsConfigured);
m_BtnFrpAutoPort.EnableWindow(frpEnabled && frpsConfigured);
// 如果启用 FRP 且端口为空,自动分配一个
if (frpEnabled && frpsConfigured && m_nFrpRemotePort == 0) {
CString portText;
m_EditFrpRemotePort.GetWindowText(portText);
if (portText.IsEmpty() || _ttoi(portText) == 0) {
// 不自动分配,让用户点击"自动"或手动输入
}
}
}
void CPwdGenDlg::OnBnClickedButtonSaveLicense()
{
UpdateData(TRUE);
// 验证数据
if (m_sDeviceID.IsEmpty() || m_sPassword.IsEmpty() || m_sHMAC.IsEmpty()) {
MessageBoxL("请先生成口令!", "提示", MB_OK | MB_ICONWARNING);
return;
}
// 提示用户输入备注(可选)
CString remark;
// 这里可以弹出一个简单的输入对话框获取备注,或者直接保存
// 检查是否启用了 FRP 代理
std::string frpConfig;
if (m_CheckFrpProxy.GetCheck() == BST_CHECKED && CFrpsForSubDlg::IsFrpsConfigured()) {
// 获取远程端口
CString portText;
m_EditFrpRemotePort.GetWindowText(portText);
int remotePort = _ttoi(portText);
if (remotePort < 1024 || remotePort > 65535) {
MessageBoxL("FRP 远程端口无效1024-65535", "提示", MB_OK | MB_ICONWARNING);
m_EditFrpRemotePort.SetFocus();
return;
}
// 获取 FRPS 配置
FrpsConfig frpsConfig = CFrpsForSubDlg::GetFrpsConfig();
// 生成 FRP 配置(有效期设为最大值,由 License 控制过期)
// authMode: 0 = 官方 FRP (token), 1 = 自定义 FRP (privilegeKey)
// 注意expireDate 最大 20371231因为自定义 FRP 的 timestamp 参数是 32 位 long2038 年后会溢出
frpConfig = GenerateFrpConfig(frpsConfig.server, frpsConfig.port, remotePort, frpsConfig.token, "20371231", frpsConfig.authMode);
if (!frpConfig.empty()) {
// 记录端口分配
CFrpsForSubDlg::RecordPortAllocation(remotePort, m_sDeviceID.GetString());
Mprintf("[FRP] 为序列号 %s 分配端口 %d\n", m_sDeviceID.GetString(), remotePort);
}
}
// 保存授权信息到数据库
bool success = SaveLicenseInfo(
m_sDeviceID.GetString(),
m_sPassword.GetString(),
m_sHMAC.GetString(),
remark.GetString(),
m_sAuthorization.GetString(),
frpConfig
);
if (!success) {
MessageBoxL("保存授权信息失败!", "错误", MB_OK | MB_ICONERROR);
return;
}
// 询问是否导出文件
if (MessageBoxL("授权已保存到数据库。\n\n是否同时导出为文件?\n导出后可发送给目标设备导入使用。",
"导出授权", MB_YESNO | MB_ICONQUESTION) == IDYES) {
// 构建默认文件名(使用完整 SN
CString defaultName;
defaultName.Format(_T(BRAND_LICENSE_PREFIX "_%s.lic"), m_sDeviceID.GetString());
// 弹出文件保存对话框
CString filter = _T(BRAND_LICENSE_DESC " (*.lic)|*.lic|All Files (*.*)|*.*||");
CString dlgTitle = _TR("保存授权文件");
CFileDialog dlg(FALSE, _T("lic"), defaultName,
OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY, filter, this);
dlg.m_ofn.lpstrTitle = dlgTitle;
if (dlg.DoModal() == IDOK) {
// 导出文件
if (ExportLicenseFile(
std::string(CT2A(dlg.GetPathName().GetString())),
std::string(CT2A(m_sDeviceID.GetString())),
std::string(CT2A(m_sPassword.GetString())),
std::string(CT2A(m_sHMAC.GetString())),
std::string(CT2A(m_sAuthorization.GetString())),
frpConfig)) {
CString msg;
msg.Format(_T("%s\n%s"), _TR("授权文件已导出到:"), dlg.GetPathName().GetString());
MessageBox(msg, _TR("导出成功"), MB_OK | MB_ICONINFORMATION);
} else {
MessageBoxL("无法写入文件", "导出失败", MB_OK | MB_ICONERROR);
}
}
} else {
// 只保存到数据库,显示成功信息
CString msg;
if (m_sAuthorization.IsEmpty()) {
msg.FormatL("授权信息已保存!\n\n序列号: %s\n口令: %s\nHMAC: %s\n\n存储位置: %s",
m_sDeviceID, m_sPassword, m_sHMAC, GetLicensesPath().c_str());
} else {
msg.FormatL("授权信息已保存!\n\n序列号: %s\n口令: %s\nHMAC: %s\nAuthorization: %s\n\n存储位置: %s",
m_sDeviceID, m_sPassword, m_sHMAC, m_sAuthorization, GetLicensesPath().c_str());
}
MessageBox(msg, _TR("保存成功"), MB_OK | MB_ICONINFORMATION);
}
}