Guest User

Untitled

a guest
Apr 17th, 2026
130
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.78 KB | None | 0 0
  1. // ============================================
  2. // HPANEL LICENSE CHECK MODULE (Decoded)
  3. // ============================================
  4.  
  5. const fs = require('fs');
  6. const path = require('path');
  7. const https = require('https');
  8. const http = require('http');
  9. const crypto = require('crypto');
  10. const os = require('os');
  11.  
  12. // Configuration file path
  13. const CONFIG_FILE = '/etc/hpanel/.license';
  14.  
  15. let LICENSE_SERVER_URL = '';
  16. let HMAC_SECRET = '';
  17.  
  18. // Load configuration from file
  19. try {
  20. const confData = fs.readFileSync(CONFIG_FILE, 'utf8');
  21. const conf = {};
  22.  
  23. confData.split('\n').forEach(line => {
  24. const [key, ...values] = line.split('=');
  25. if (key && values.length) {
  26. conf[key.trim()] = values.join('=').trim();
  27. }
  28. });
  29.  
  30. LICENSE_SERVER_URL = conf['V_EP'] || '';
  31. HMAC_SECRET = conf['V_SK'] || '';
  32. } catch (e) {
  33. // Config file not found
  34. }
  35.  
  36. // Cache file location
  37. const CACHE_FILE = process.env.LICENSE_CACHE_PATH || '/etc/hpanel/license.cache';
  38.  
  39. // Check interval (default 1 hour, converted to milliseconds)
  40. const CHECK_INTERVAL_MS = ((parseInt(process.env.LICENSE_CHECK_INTERVAL) || 1) * 60 * 60 * 1000);
  41.  
  42. // Panel version
  43. const PANEL_VERSION = process.env.PANEL_VERSION || '1.0.0';
  44.  
  45. // Offline grace period: 72 hours
  46. const OFFLINE_GRACE_HOURS = 72;
  47.  
  48. // ============================================
  49. // LICENSE STATE OBJECT
  50. // ============================================
  51. let licenseState = {
  52. valid: false,
  53. plan: null,
  54. limits: null,
  55. features: null,
  56. expires_at: null,
  57. grace: false,
  58. last_check: null,
  59. last_success: null,
  60. error: null,
  61. offline_mode: false
  62. };
  63.  
  64. // IP caching
  65. let cachedPublicIp = null;
  66. let ipCacheTime = 0;
  67. const IP_CACHE_TTL = 3600000; // 1 hour
  68.  
  69. // ============================================
  70. // GET SERVER PUBLIC IP ADDRESS
  71. // ============================================
  72. async function getServerIp() {
  73. // Return cached IP if still valid
  74. if (cachedPublicIp && (Date.now() - ipCacheTime) < IP_CACHE_TTL) {
  75. return cachedPublicIp;
  76. }
  77.  
  78. // List of IP detection services
  79. const ipServices = [
  80. 'https://api.ipify.org',
  81. 'https://icanhazip.com',
  82. 'https://api.ip.sb/ip',
  83. 'https://checkip.amazonaws.com'
  84. ];
  85.  
  86. for (const service of ipServices) {
  87. try {
  88. const ip = await new Promise((resolve, reject) => {
  89. const httpModule = service.startsWith('https') ? https : http;
  90.  
  91. const req = httpModule.get(service, { timeout: 5000 }, (res) => {
  92. let data = '';
  93.  
  94. res.on('data', chunk => data += chunk);
  95.  
  96. res.on('end', () => {
  97. const ip = data.trim();
  98. // Validate IPv4 format
  99. if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip)) {
  100. resolve(ip);
  101. } else {
  102. reject(new Error('Invalid IP format'));
  103. }
  104. });
  105. });
  106.  
  107. req.on('error', reject);
  108. req.on('timeout', () => {
  109. req.destroy();
  110. reject(new Error('Timeout'));
  111. });
  112. });
  113.  
  114. // Cache the result
  115. cachedPublicIp = ip;
  116. ipCacheTime = Date.now();
  117. console.log(`[License] Detected public IP: ${ip} (via ${service})`);
  118. return ip;
  119.  
  120. } catch (e) {
  121. continue; // Try next service
  122. }
  123. }
  124.  
  125. console.error('[License] Could not detect public IP from any service');
  126.  
  127. // Fallback: try to get from network interfaces
  128. try {
  129. const networkInterfaces = os.networkInterfaces();
  130.  
  131. for (const ifaceName of Object.keys(networkInterfaces)) {
  132. for (const iface of networkInterfaces[ifaceName]) {
  133. if (!iface.internal && iface.family === 'IPv4') {
  134. return iface.address;
  135. }
  136. }
  137. }
  138. } catch (e) {
  139. // Ignore errors
  140. }
  141.  
  142. return 'unknown';
  143. }
  144.  
  145. // ============================================
  146. // GET SERVER HOSTNAME
  147. // ============================================
  148. function getHostname() {
  149. return os.hostname();
  150. }
  151.  
  152. // ============================================
  153. // VERIFY HMAC SIGNATURE
  154. // ============================================
  155. function verifyHmac(responseData) {
  156. // Check required fields exist
  157. if (!HMAC_SECRET || !responseData.hmac || !responseData.timestamp) {
  158. return false;
  159. }
  160.  
  161. // Check timestamp is within 5 minutes (prevent replay attacks)
  162. const nowSeconds = Math.floor(Date.now() / 1000);
  163. const timestampAge = Math.abs(nowSeconds - responseData.timestamp);
  164.  
  165. if (timestampAge > 300) { // 5 minutes
  166. return false;
  167. }
  168.  
  169. // Build string to verify: key|ip|valid|plan|expires|features|timestamp
  170. const verifyString = [
  171. responseData.license_key || '',
  172. responseData.server_ip || '',
  173. String(responseData.valid),
  174. responseData.plan || '',
  175. responseData.expires_at || '',
  176. String(responseData.timestamp)
  177. ].join('|');
  178.  
  179. // Calculate expected HMAC
  180. const expectedHmac = crypto
  181. .createHmac('sha256', HMAC_SECRET)
  182. .update(verifyString)
  183. .digest('hex');
  184.  
  185. // Compare securely (timing-safe)
  186. try {
  187. return crypto.timingSafeEqual(
  188. Buffer.from(responseData.hmac, 'hex'),
  189. Buffer.from(expectedHmac, 'hex')
  190. );
  191. } catch (e) {
  192. return false;
  193. }
  194. }
  195.  
  196. // ============================================
  197. // MAKE HTTP REQUEST TO LICENSE SERVER
  198. // ============================================
  199. function makeRequest(endpoint, data) {
  200. return new Promise((resolve, reject) => {
  201. const options = {
  202. hostname: new URL(endpoint, LICENSE_SERVER_URL).hostname,
  203. port: new URL(endpoint, LICENSE_SERVER_URL).port || (isHttps ? 443 : 80),
  204. pathname: new URL(endpoint, LICENSE_SERVER_URL).pathname,
  205. method: 'POST',
  206. headers: {
  207. 'Content-Type': 'application/json',
  208. 'Content-Length': Buffer.byteLength(JSON.stringify(data)),
  209. 'User-Agent': `HPanel/${PANEL_VERSION}`
  210. },
  211. timeout: 15000,
  212. rejectUnauthorized: process.env.NODE_ENV === 'production'
  213. };
  214.  
  215. const isHttps = new URL(endpoint, LICENSE_SERVER_URL).protocol === 'https:';
  216. const httpModule = isHttps ? https : http;
  217.  
  218. const req = httpModule.request(options, (res) => {
  219. let responseBody = '';
  220.  
  221. res.on('data', chunk => responseBody += chunk);
  222.  
  223. res.on('end', () => {
  224. try {
  225. resolve({
  226. status: res.statusCode,
  227. data: JSON.parse(responseBody)
  228. });
  229. } catch (e) {
  230. reject(new Error('Invalid JSON response from license server'));
  231. }
  232. });
  233. });
  234.  
  235. req.on('error', reject);
  236.  
  237. req.on('timeout', () => {
  238. req.destroy();
  239. reject(new Error('Request timeout'));
  240. });
  241.  
  242. req.write(JSON.stringify(data));
  243. req.end();
  244. });
  245. }
  246.  
  247. // ============================================
  248. // LOAD CACHED LICENSE STATE
  249. // ============================================
  250. function loadCache() {
  251. try {
  252. if (fs.existsSync(CACHE_FILE)) {
  253. return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
  254. }
  255. } catch (e) {
  256. console.error('[License] Failed to load cache:', e.message);
  257. }
  258. return null;
  259. }
  260.  
  261. // ============================================
  262. // SAVE LICENSE STATE TO CACHE
  263. // ============================================
  264. function saveCache(state) {
  265. try {
  266. const cacheDir = path.dirname(CACHE_FILE);
  267.  
  268. // Create directory if it doesn't exist
  269. if (!fs.existsSync(cacheDir)) {
  270. fs.mkdirSync(cacheDir, { recursive: true });
  271. }
  272.  
  273. fs.writeFileSync(CACHE_FILE, JSON.stringify({
  274. ...state,
  275. cached_at: new Date().toISOString()
  276. }, null, 2));
  277. } catch (e) {
  278. console.error('[License] Failed to save cache:', e.message);
  279. }
  280. }
  281.  
  282. // ============================================
  283. // GET CURRENT USAGE COUNTS FROM DATABASE
  284. // ============================================
  285. async function getUsageCounts() {
  286. try {
  287. const db = require('./database');
  288.  
  289. const accountsResult = await db.query('SELECT COUNT(*) as count FROM users');
  290. const domainsResult = await db.query('SELECT COUNT(*) as count FROM domains');
  291.  
  292. return {
  293. current_accounts: parseInt(accountsResult.rows[0].count) || 0,
  294. current_domains: parseInt(domainsResult.rows[0].count) || 0
  295. };
  296. } catch (e) {
  297. console.error('[License] Failed to get usage counts:', e.message);
  298. return {
  299. current_accounts: 0,
  300. current_domains: 0
  301. };
  302. }
  303. }
  304.  
  305. // ============================================
  306. // MAIN LICENSE VERIFICATION FUNCTION
  307. // ============================================
  308. async function verifyLicense() {
  309. const serverIp = await getServerIp();
  310. const hostname = getHostname();
  311. const usageCounts = await getUsageCounts();
  312.  
  313. // Build request payload
  314. const payload = {
  315. server_ip: serverIp,
  316. hostname: hostname,
  317. panel_version: PANEL_VERSION,
  318. current_accounts: usageCounts.current_accounts,
  319. current_domains: usageCounts.current_domains
  320. };
  321.  
  322. console.log(`[License] Verifying ${serverIp} (accounts: ${usageCounts.current_accounts}, domains: ${usageCounts.current_domains})`);
  323.  
  324. try {
  325. // Make request to license server
  326. const { status, data } = await makeRequest('/api/license/verify', payload);
  327.  
  328. // Verify HMAC signature if secret is configured
  329. if (HMAC_SECRET && data.hmac) {
  330. if (!verifyHmac(data)) {
  331. console.error('[License] HMAC verification failed:', {
  332. license_key: data.license_key || '',
  333. server_ip: data.server_ip || '',
  334. valid: String(data.valid),
  335. plan: data.plan || '',
  336. expires_at: data.expires_at || '',
  337. timestamp: data.timestamp
  338. });
  339. console.error('[License] Response may have been tampered with!');
  340. }
  341. }
  342.  
  343. if (data.valid) {
  344. // License is valid
  345. licenseState = {
  346. valid: true,
  347. plan: data.plan,
  348. limits: data.limits || {},
  349. features: data.features || {},
  350. expires_at: data.expires_at,
  351. grace: data.grace || false,
  352. last_check: new Date().toISOString(),
  353. last_success: new Date().toISOString(),
  354. error: null,
  355. offline_mode: false
  356. };
  357.  
  358. saveCache(licenseState);
  359. console.log(`[License] ✅ Valid license! Plan: ${data.plan}, Grace: ${data.grace}`);
  360.  
  361. } else {
  362. // License invalid
  363. licenseState = {
  364. valid: false,
  365. plan: null,
  366. limits: null,
  367. features: null,
  368. expires_at: null,
  369. grace: false,
  370. last_check: new Date().toISOString(),
  371. last_success: licenseState.last_success,
  372. error: data.error || 'License invalid',
  373. offline_mode: false
  374. };
  375. console.warn(`[License] ❌ Invalid — ${data.error}`);
  376. }
  377.  
  378. } catch (e) {
  379. console.error(`[License] Verification failed (server unreachable): ${e.message}`);
  380.  
  381. // Try to use cached license
  382. const cached = loadCache();
  383.  
  384. if (cached && cached.valid) {
  385. const lastSuccess = new Date(cached.expires_at || cached.last_success);
  386. const hoursSinceLastCheck = (Date.now() - lastSuccess.getTime()) / (1000 * 60 * 60);
  387.  
  388. if (hoursSinceLastCheck < OFFLINE_GRACE_HOURS) {
  389. // Within grace period - allow offline mode
  390. licenseState = {
  391. ...cached,
  392. last_check: new Date().toISOString(),
  393. offline_mode: true,
  394. error: `License server unreachable. Operating in offline mode from cache (${Math.round(hoursSinceLastCheck)}h ago)`
  395. };
  396. console.log(`[License] Using cached license (${Math.round(hoursSinceLastCheck)}h old)`);
  397. } else {
  398. // Grace period expired
  399. licenseState = {
  400. valid: false,
  401. plan: null,
  402. limits: null,
  403. features: null,
  404. expires_at: null,
  405. grace: false,
  406. last_check: new Date().toISOString(),
  407. last_success: cached.last_success,
  408. error: `License server unreachable and grace period exceeded (${Math.round(hoursSinceLastCheck)}h)`,
  409. offline_mode: true
  410. };
  411. console.warn(`[License] Grace period exceeded (${Math.round(hoursSinceLastCheck)}h)`);
  412. }
  413. } else {
  414. // No cache available
  415. licenseState.last_check = new Date().toISOString();
  416. licenseState.error = 'License server unreachable and no cached license available';
  417. }
  418. }
  419.  
  420. return licenseState;
  421. }
  422.  
  423. // ============================================
  424. // ACTIVATE NEW LICENSE KEY
  425. // ============================================
  426. async function activateLicense(licenseKey) {
  427. const ENDPOINT = '/api/license/activate';
  428. const ERROR_MSG = 'License server unreachable';
  429.  
  430. const serverIp = await getServerIp();
  431. const hostname = getHostname();
  432.  
  433. try {
  434. const payload = {
  435. license_key: licenseKey,
  436. server_ip: serverIp,
  437. hostname: hostname,
  438. panel_version: PANEL_VERSION
  439. };
  440.  
  441. const { status, data } = await makeRequest(ENDPOINT, payload);
  442.  
  443. if (data.success) {
  444. console.log(`[License] ✅ License activated: ${licenseKey.substring(0, 8)}...`);
  445. return {
  446. success: true,
  447. message: data.message
  448. };
  449. } else {
  450. console.error(`[License] ❌ Activation failed: ${data.error}`);
  451. return {
  452. success: false,
  453. message: data.error
  454. };
  455. }
  456. } catch (e) {
  457. console.error(`[License] Activation error: ${e.message}`);
  458. return {
  459. success: false,
  460. message: ERROR_MSG
  461. };
  462. }
  463. }
  464.  
  465. // ============================================
  466. // GET CURRENT LICENSE STATE
  467. // ============================================
  468. function getLicenseState() {
  469. return {
  470. ...licenseState,
  471. server_ip: cachedPublicIp || null
  472. };
  473. }
  474.  
  475. // ============================================
  476. // CHECK IF FEATURE IS ENABLED
  477. // ============================================
  478. function hasFeature(featureName) {
  479. if (!licenseState.valid) return false;
  480. return licenseState.features && licenseState.features[featureName] === true;
  481. }
  482.  
  483. // ============================================
  484. // CHECK USAGE LIMIT
  485. // ============================================
  486. function checkLimit(limitType, currentValue) {
  487. const result = {
  488. allowed: false,
  489. reason: 'License not valid or no limits defined'
  490. };
  491.  
  492. if (!licenseState.valid || !licenseState.limits) return result;
  493.  
  494. const maxAllowed = licenseState.limits[limitType];
  495.  
  496. // Unlimited (-1) or undefined
  497. if (maxAllowed === undefined || maxAllowed === null || maxAllowed === -1) {
  498. return { allowed: true };
  499. }
  500.  
  501. if (currentValue >= maxAllowed) {
  502. const limitNames = {
  503. accounts: 'accounts',
  504. domains: 'domains'
  505. };
  506.  
  507. const displayName = limitNames[limitType] || limitType;
  508.  
  509. return {
  510. allowed: false,
  511. reason: `You have reached the maximum number of ${displayName} (${currentValue}/${maxAllowed}). Please remove some items or upgrade your license plan.`
  512. };
  513. }
  514.  
  515. return {
  516. allowed: true,
  517. used: currentValue,
  518. max: maxAllowed
  519. };
  520. }
  521.  
  522. // ============================================
  523. // PERIODIC LICENSE CHECKING
  524. // ============================================
  525. let checkInterval = null;
  526.  
  527. function startPeriodicCheck() {
  528. if (checkInterval) clearInterval(checkInterval);
  529.  
  530. checkInterval = setInterval(() => {
  531. verifyLicense().catch(e => {
  532. console.error('[License] Periodic check error:', e.message);
  533. });
  534. }, CHECK_INTERVAL_MS);
  535.  
  536. console.log(`[License] Periodic check started (every ${CHECK_INTERVAL_MS / 3600000}h)`);
  537. }
  538.  
  539. function stopPeriodicCheck() {
  540. if (checkInterval) {
  541. clearInterval(checkInterval);
  542. checkInterval = null;
  543. }
  544. }
  545.  
  546. // ============================================
  547. // EXPORT PUBLIC API
  548. // ============================================
  549. module.exports = {
  550. verifyLicense,
  551. activateLicense,
  552. getLicenseState,
  553. hasFeature,
  554. checkLimit,
  555. startPeriodicCheck,
  556. stopPeriodicCheck
  557. };
Advertisement
Add Comment
Please, Sign In to add comment