diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index 1e174dd..9389e06 100644 Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index ec79a5e..ee99f1a 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -23,6 +23,7 @@ #include "InputDlg.h" #include "CPasswordDlg.h" #include "pwd_gen.h" +#include "DateVerify.h" #ifdef _DEBUG #define new DEBUG_NEW @@ -196,6 +197,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_MESSAGE(WM_OPENKEYBOARDDIALOG, OnOpenKeyboardDialog) ON_WM_HELPINFO() ON_COMMAND(ID_ONLINE_SHARE, &CMy2015RemoteDlg::OnOnlineShare) + ON_COMMAND(ID_TOOL_AUTH, &CMy2015RemoteDlg::OnToolAuth) END_MESSAGE_MAP() @@ -925,98 +927,16 @@ std::string joinString(const std::vector& tokens, char delimiter) { return oss.str(); } -#define REG_SETTINGS "Software\\YAMA\\Settings" -// 写入字符串配置(多字节版) -bool WriteAppSettingA(const std::string& keyName, const std::string& value) { - HKEY hKey; +bool CMy2015RemoteDlg::CheckValid() { + DateVerify verify; +#ifdef _DEBUG + BOOL isTrail = verify.isTrail(0); +#else + BOOL isTrail = verify.isTrail(14); +#endif - LONG result = RegCreateKeyExA( - HKEY_CURRENT_USER, - REG_SETTINGS, - 0, - NULL, - 0, - KEY_WRITE, - NULL, - &hKey, - NULL - ); - - if (result != ERROR_SUCCESS) { - Mprintf("无法创建或打开注册表键,错误码: %d\n", result); - return false; - } - - result = RegSetValueExA( - hKey, - keyName.c_str(), - 0, - REG_SZ, - reinterpret_cast(value.c_str()), - static_cast(value.length() + 1) - ); - - RegCloseKey(hKey); - return result == ERROR_SUCCESS; -} - -// 读取字符串配置(多字节版) -bool ReadAppSettingA(const std::string& keyName, std::string& outValue) { - HKEY hKey; - - LONG result = RegOpenKeyExA( - HKEY_CURRENT_USER, - REG_SETTINGS, - 0, - KEY_READ, - &hKey - ); - - if (result != ERROR_SUCCESS) { - return false; - } - - char buffer[256]; - DWORD bufferSize = sizeof(buffer); - DWORD type = 0; - - result = RegQueryValueExA( - hKey, - keyName.c_str(), - nullptr, - &type, - reinterpret_cast(buffer), - &bufferSize - ); - - RegCloseKey(hKey); - - if (result == ERROR_SUCCESS && type == REG_SZ) { - outValue = buffer; - return true; - } - - return false; -} - -// 这个函数用来控制试用的次数,并不严谨;如果编译源码,则可以视情况自己去掉,采用其他更严密的授权方法 -int CanBuildClient() { - std::string freeTrail; - auto b = ReadAppSettingA("free_trial", freeTrail); - if (!b || freeTrail.empty()) - freeTrail = "10"; - return atoi(freeTrail.c_str()); -} - -bool UpdateFreeTrial(int n) { - return WriteAppSettingA("free_trial", std::to_string(n)); -} - -void CMy2015RemoteDlg::OnOnlineBuildClient() -{ - auto n = CanBuildClient(); - if (n<=0) { + if (!isTrail) { auto THIS_APP = (CMy2015RemoteApp*)AfxGetApp(); auto settings = "settings", pwdKey = "Password"; // 验证口令 @@ -1029,7 +949,7 @@ void CMy2015RemoteDlg::OnOnlineBuildClient() dlg.m_sDeviceID = deviceID.c_str(); dlg.m_sPassword = pwd; if (pwd.IsEmpty() && IDOK != dlg.DoModal() || dlg.m_sPassword.IsEmpty()) - return; + return false; // 密码形式:20250209 - 20350209: SHA256 auto v = splitString(dlg.m_sPassword.GetBuffer(), '-'); @@ -1037,7 +957,7 @@ void CMy2015RemoteDlg::OnOnlineBuildClient() { THIS_APP->m_iniFile.SetStr(settings, pwdKey, ""); MessageBox("格式错误,请重新申请口令!", "提示", MB_ICONINFORMATION); - return; + return false; } std::vector subvector(v.begin() + 2, v.end()); std::string password = v[0] + " - " + v[1] + ": " + PWD_HASH256; @@ -1049,7 +969,7 @@ void CMy2015RemoteDlg::OnOnlineBuildClient() if (pwd.IsEmpty() || (IDOK != dlg.DoModal() || hash256 != fixedKey)) { if (!dlg.m_sPassword.IsEmpty()) MessageBox("口令错误, 无法生成服务端!", "提示", MB_ICONWARNING); - return; + return false; } } // 判断是否过期 @@ -1059,19 +979,30 @@ void CMy2015RemoteDlg::OnOnlineBuildClient() if (curDate < v[0] || curDate > v[1]) { THIS_APP->m_iniFile.SetStr(settings, pwdKey, ""); MessageBox("口令过期,请重新申请口令!", "提示", MB_ICONINFORMATION); - return; + return false; } if (dlg.m_sPassword != pwd) THIS_APP->m_iniFile.SetStr(settings, pwdKey, dlg.m_sPassword); } + return true; +} + +void CMy2015RemoteDlg::OnOnlineBuildClient() +{ + // 给新编译的程序14天试用期,过期之后生成服务端需要申请"序列号"; + // 如果要对其他功能乃至整个程序启动授权逻辑,将下述if语句添加到相应地方即可。 + // 序列号包含授权日期范围,确保一机一码;授权逻辑会检测计算机日期未被篡改! + // 注释下面 if 语句可以屏蔽该授权逻辑. + // 2025/04/20 + if (!CheckValid()) + return; + // TODO: 在此添加命令处理程序代码 CBuildDlg Dlg; Dlg.m_strIP = ((CMy2015RemoteApp*)AfxGetApp())->m_iniFile.GetStr("settings", "localIp", ""); int Port = ((CMy2015RemoteApp*)AfxGetApp())->m_iniFile.GetInt("settings", "ghost"); Dlg.m_strPort = Port <= 0 ? "6543" : std::to_string(Port).c_str(); - if (IDOK == Dlg.DoModal() && n > 0) { - UpdateFreeTrial(n - 1); - } + Dlg.DoModal(); } @@ -1806,3 +1737,15 @@ void CMy2015RemoteDlg::OnOnlineShare() memcpy(bToken + 2, dlg.m_str, dlg.m_str.GetLength()); SendSelectedCommand(bToken, sizeof(bToken)); } + + +void CMy2015RemoteDlg::OnToolAuth() +{ + CPwdGenDlg dlg; + std::string hardwareID = getHardwareID(); + std::string hashedID = hashSHA256(hardwareID); + std::string deviceID = getFixedLengthID(hashedID); + dlg.m_sDeviceID = deviceID.c_str(); + + dlg.DoModal(); +} diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 7bd8e06..d6b2f42 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -80,6 +80,7 @@ public: BOOL isClosed; CBitmap m_bmOnline[4]; + bool CheckValid(); afx_msg void OnTimer(UINT_PTR nIDEvent); afx_msg void OnClose(); void Release(); @@ -121,4 +122,5 @@ public: afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo); virtual BOOL PreTranslateMessage(MSG* pMsg); afx_msg void OnOnlineShare(); + afx_msg void OnToolAuth(); }; diff --git a/server/2015Remote/CPasswordDlg.cpp b/server/2015Remote/CPasswordDlg.cpp index 9543699..85ff29d 100644 --- a/server/2015Remote/CPasswordDlg.cpp +++ b/server/2015Remote/CPasswordDlg.cpp @@ -50,3 +50,89 @@ BOOL CPasswordDlg::OnInitDialog() return TRUE; // return TRUE unless you set the focus to a control // 寮傚父: OCX 灞炴ч〉搴旇繑鍥 FALSE } + + +// CPasswordDlg 娑堟伅澶勭悊绋嬪簭 + +IMPLEMENT_DYNAMIC(CPwdGenDlg, CDialogEx) + +CPwdGenDlg::CPwdGenDlg(CWnd* pParent /*=nullptr*/) + : CDialogEx(IDD_DIALOG_KEYGEN, pParent) + , m_sDeviceID(_T("")) + , m_sPassword(_T("")) + , m_sUserPwd(_T("")) + , m_ExpireTm(COleDateTime::GetCurrentTime()) + , m_StartTm(COleDateTime::GetCurrentTime()) +{ + +} + +CPwdGenDlg::~CPwdGenDlg() +{ +} + +void CPwdGenDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogEx::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, 37); + 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); +} + + +BEGIN_MESSAGE_MAP(CPwdGenDlg, CDialogEx) + ON_BN_CLICKED(IDC_BUTTON_GENKEY, &CPwdGenDlg::OnBnClickedButtonGenkey) +END_MESSAGE_MAP() + + +void CPwdGenDlg::OnBnClickedButtonGenkey() +{ + // TODO: 鍦ㄦ娣诲姞鎺т欢閫氱煡澶勭悊绋嬪簭浠g爜 + UpdateData(TRUE); + if (m_sUserPwd.IsEmpty())return; + std::string pwdHash = hashSHA256(m_sUserPwd.GetString()); + if (pwdHash != PWD_HASH256) { + Mprintf("hashSHA256 [%s]: %s\n", m_sUserPwd, pwdHash.c_str()); + MessageBoxA("鎮ㄨ緭鍏ョ殑瀵嗙爜涓嶆纭紝鏃犳硶鐢熸垚鍙d护!", "鎻愮ず", MB_OK | MB_ICONWARNING); + return; + } + CString strBeginDate = m_StartTm.Format("%Y%m%d"); + CString strEndDate = m_ExpireTm.Format("%Y%m%d"); + // 瀵嗙爜褰㈠紡锛20250209 - 20350209: SHA256 + std::string password = std::string(strBeginDate.GetString()) + " - " + strEndDate.GetBuffer() + ": " + PWD_HASH256; + std::string finalKey = deriveKey(password, m_sDeviceID.GetString()); + std::string fixedKey = strBeginDate.GetString() + std::string("-") + strEndDate.GetBuffer() + std::string("-") + + getFixedLengthID(finalKey); + m_EditPassword.SetWindowTextA(fixedKey.c_str()); + std::string hardwareID = getHardwareID(); + std::string hashedID = hashSHA256(hardwareID); + std::string deviceID = getFixedLengthID(hashedID); + if (deviceID == m_sDeviceID.GetString()) { // 鎺堟潈鐨勬槸褰撳墠涓绘帶绋嬪簭 + auto THIS_APP = (CMy2015RemoteApp*)AfxGetApp(); + auto settings = "settings", pwdKey = "Password"; + THIS_APP->m_iniFile.SetStr(settings, pwdKey, fixedKey.c_str()); + } +} + + +BOOL CPwdGenDlg::OnInitDialog() +{ + CDialogEx::OnInitDialog(); + + // TODO: 鍦ㄦ娣诲姞棰濆鐨勫垵濮嬪寲 + m_hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_PASSWORD)); + SetIcon(m_hIcon, FALSE); + + return TRUE; // return TRUE unless you set the focus to a control + // 寮傚父: OCX 灞炴ч〉搴旇繑鍥 FALSE +} diff --git a/server/2015Remote/CPasswordDlg.h b/server/2015Remote/CPasswordDlg.h index f8dbef6..7703d7b 100644 --- a/server/2015Remote/CPasswordDlg.h +++ b/server/2015Remote/CPasswordDlg.h @@ -5,6 +5,7 @@ #include "Resource.h" // 瀵嗙爜鐨勫搱甯屽 +// 鎻愮ず锛氳鐢╤ashSHA256鍑芥暟鑾峰緱瀵嗙爜鐨勫搱甯屽硷紝浣犲簲璇ョ敤鑷繁鐨勫瘑鐮佺敓鎴愬搱甯屽硷紝骞舵浛鎹㈣繖涓粯璁ゅ. #define PWD_HASH256 "61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43" // CPasswordDlg 瀵硅瘽妗 @@ -31,3 +32,36 @@ public: CString m_sPassword; virtual BOOL OnInitDialog(); }; + + +class CPwdGenDlg : public CDialogEx +{ + DECLARE_DYNAMIC(CPwdGenDlg) + +public: + CPwdGenDlg(CWnd* pParent = nullptr); // 鏍囧噯鏋勯犲嚱鏁 + virtual ~CPwdGenDlg(); + + enum { + IDD = IDD_DIALOG_KEYGEN + }; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 鏀寔 + + DECLARE_MESSAGE_MAP() +public: + HICON m_hIcon; + CEdit m_EditDeviceID; + CEdit m_EditPassword; + CEdit m_EditUserPwd; + CString m_sDeviceID; + CString m_sPassword; + CString m_sUserPwd; + afx_msg void OnBnClickedButtonGenkey(); + CDateTimeCtrl m_PwdExpireDate; + COleDateTime m_ExpireTm; + CDateTimeCtrl m_StartDate; + COleDateTime m_StartTm; + virtual BOOL OnInitDialog(); +}; diff --git a/server/2015Remote/DateVerify.h b/server/2015Remote/DateVerify.h new file mode 100644 index 0000000..d39af07 --- /dev/null +++ b/server/2015Remote/DateVerify.h @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include +#include +#pragma comment(lib, "ws2_32.lib") + +// 中国大陆优化的NTP服务器列表 +const char* CN_NTP_SERVERS[] = { + "ntp.aliyun.com", + "time1.aliyun.com", + "ntp1.tencent.com", + "time.edu.cn", + "ntp.tuna.tsinghua.edu.cn", + "cn.ntp.org.cn", +}; +const int CN_NTP_COUNT = sizeof(CN_NTP_SERVERS) / sizeof(CN_NTP_SERVERS[0]); +const int NTP_PORT = 123; +const uint64_t NTP_EPOCH_OFFSET = 2208988800ULL; + +// 检测程序是否处于试用期 +class DateVerify +{ +private: + // 初始化Winsock + bool initWinsock() { + WSADATA wsaData; + return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; + } + + // 从指定NTP服务器获取时间 + time_t getTimeFromServer(const char* server) { + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) return 0; + + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(NTP_PORT); + + // 解析主机名 + hostent* host = gethostbyname(server); + if (!host) { + closesocket(sock); + return 0; + } + serverAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr_list[0]); + + // 设置超时 + DWORD timeout = 2000; // 2秒超时 + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); + + // 准备NTP请求包 + char ntpPacket[48] = { 0 }; + ntpPacket[0] = 0x1B; // LI=0, VN=3, Mode=3 + + // 发送请求 + if (sendto(sock, ntpPacket, sizeof(ntpPacket), 0, + (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { + closesocket(sock); + return 0; + } + + // 接收响应 + if (recv(sock, ntpPacket, sizeof(ntpPacket), 0) <= 0) { + closesocket(sock); + return 0; + } + + closesocket(sock); + + // 解析NTP时间 + uint32_t ntpTime = ntohl(*((uint32_t*)(ntpPacket + 40))); + return ntpTime - NTP_EPOCH_OFFSET; + } + + // 获取网络时间(尝试多个服务器) + time_t getNetworkTimeInChina() { + if (!initWinsock()) return 0; + + time_t result = 0; + for (int i = 0; i < CN_NTP_COUNT && result == 0; i++) { + result = getTimeFromServer(CN_NTP_SERVERS[i]); + if (result != 0) { + break; + } + } + + WSACleanup(); + return result; + } + + // 将月份缩写转换为数字(1-12) + int monthAbbrevToNumber(const std::string& month) { + static const std::map months = { + {"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, + {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, + {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12} + }; + auto it = months.find(month); + return (it != months.end()) ? it->second : 0; + } + + // 解析__DATE__字符串为tm结构 + tm parseCompileDate(const char* compileDate) { + tm tmCompile = { 0 }; + std::string monthStr(compileDate, 3); + std::string dayStr(compileDate + 4, 2); + std::string yearStr(compileDate + 7, 4); + + tmCompile.tm_year = std::stoi(yearStr) - 1900; + tmCompile.tm_mon = monthAbbrevToNumber(monthStr) - 1; + tmCompile.tm_mday = std::stoi(dayStr); + + return tmCompile; + } + + // 计算两个日期之间的天数差 + int daysBetweenDates(const tm& date1, const tm& date2) { + auto timeToTimePoint = [](const tm& tmTime) { + std::time_t tt = mktime(const_cast(&tmTime)); + return std::chrono::system_clock::from_time_t(tt); + }; + + auto tp1 = timeToTimePoint(date1); + auto tp2 = timeToTimePoint(date2); + + auto duration = tp1 > tp2 ? tp1 - tp2 : tp2 - tp1; + return std::chrono::duration_cast(duration).count() / 24; + } + + // 获取当前日期 + tm getCurrentDate() { + std::time_t now = std::time(nullptr); + tm tmNow = *std::localtime(&now); + tmNow.tm_hour = 0; + tmNow.tm_min = 0; + tmNow.tm_sec = 0; + return tmNow; + } + + // 验证本地日期是否被修改 + bool isLocalDateModified() { + time_t networkTime = getNetworkTimeInChina(); + if (networkTime == 0) { + return true; // 无法验证 + } + + time_t localTime = time(nullptr); + double diffDays = difftime(networkTime, localTime) / 86400.0; + + // 允许±1天的误差(考虑网络延迟和时区等因素) + if (fabs(diffDays) > 1.0) { + return true; + } + + return false; + } + +public: + + bool isTrail(int trailDays=7) { + if (isLocalDateModified()) + return false; + + tm tmCompile = parseCompileDate(__DATE__), tmCurrent = getCurrentDate(); + + // 计算天数差 + int daysDiff = daysBetweenDates(tmCompile, tmCurrent); + + return daysDiff <= trailDays; + } +}; diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index bcdf5b2..1317ab7 100644 Binary files a/server/2015Remote/resource.h and b/server/2015Remote/resource.h differ