Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ============================================
- // HPANEL LICENSE CHECK MODULE (Decoded)
- // ============================================
- const fs = require('fs');
- const path = require('path');
- const https = require('https');
- const http = require('http');
- const crypto = require('crypto');
- const os = require('os');
- // Configuration file path
- const CONFIG_FILE = '/etc/hpanel/.license';
- let LICENSE_SERVER_URL = '';
- let HMAC_SECRET = '';
- // Load configuration from file
- try {
- const confData = fs.readFileSync(CONFIG_FILE, 'utf8');
- const conf = {};
- confData.split('\n').forEach(line => {
- const [key, ...values] = line.split('=');
- if (key && values.length) {
- conf[key.trim()] = values.join('=').trim();
- }
- });
- LICENSE_SERVER_URL = conf['V_EP'] || '';
- HMAC_SECRET = conf['V_SK'] || '';
- } catch (e) {
- // Config file not found
- }
- // Cache file location
- const CACHE_FILE = process.env.LICENSE_CACHE_PATH || '/etc/hpanel/license.cache';
- // Check interval (default 1 hour, converted to milliseconds)
- const CHECK_INTERVAL_MS = ((parseInt(process.env.LICENSE_CHECK_INTERVAL) || 1) * 60 * 60 * 1000);
- // Panel version
- const PANEL_VERSION = process.env.PANEL_VERSION || '1.0.0';
- // Offline grace period: 72 hours
- const OFFLINE_GRACE_HOURS = 72;
- // ============================================
- // LICENSE STATE OBJECT
- // ============================================
- let licenseState = {
- valid: false,
- plan: null,
- limits: null,
- features: null,
- expires_at: null,
- grace: false,
- last_check: null,
- last_success: null,
- error: null,
- offline_mode: false
- };
- // IP caching
- let cachedPublicIp = null;
- let ipCacheTime = 0;
- const IP_CACHE_TTL = 3600000; // 1 hour
- // ============================================
- // GET SERVER PUBLIC IP ADDRESS
- // ============================================
- async function getServerIp() {
- // Return cached IP if still valid
- if (cachedPublicIp && (Date.now() - ipCacheTime) < IP_CACHE_TTL) {
- return cachedPublicIp;
- }
- // List of IP detection services
- const ipServices = [
- 'https://api.ipify.org',
- 'https://icanhazip.com',
- 'https://api.ip.sb/ip',
- 'https://checkip.amazonaws.com'
- ];
- for (const service of ipServices) {
- try {
- const ip = await new Promise((resolve, reject) => {
- const httpModule = service.startsWith('https') ? https : http;
- const req = httpModule.get(service, { timeout: 5000 }, (res) => {
- let data = '';
- res.on('data', chunk => data += chunk);
- res.on('end', () => {
- const ip = data.trim();
- // Validate IPv4 format
- if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip)) {
- resolve(ip);
- } else {
- reject(new Error('Invalid IP format'));
- }
- });
- });
- req.on('error', reject);
- req.on('timeout', () => {
- req.destroy();
- reject(new Error('Timeout'));
- });
- });
- // Cache the result
- cachedPublicIp = ip;
- ipCacheTime = Date.now();
- console.log(`[License] Detected public IP: ${ip} (via ${service})`);
- return ip;
- } catch (e) {
- continue; // Try next service
- }
- }
- console.error('[License] Could not detect public IP from any service');
- // Fallback: try to get from network interfaces
- try {
- const networkInterfaces = os.networkInterfaces();
- for (const ifaceName of Object.keys(networkInterfaces)) {
- for (const iface of networkInterfaces[ifaceName]) {
- if (!iface.internal && iface.family === 'IPv4') {
- return iface.address;
- }
- }
- }
- } catch (e) {
- // Ignore errors
- }
- return 'unknown';
- }
- // ============================================
- // GET SERVER HOSTNAME
- // ============================================
- function getHostname() {
- return os.hostname();
- }
- // ============================================
- // VERIFY HMAC SIGNATURE
- // ============================================
- function verifyHmac(responseData) {
- // Check required fields exist
- if (!HMAC_SECRET || !responseData.hmac || !responseData.timestamp) {
- return false;
- }
- // Check timestamp is within 5 minutes (prevent replay attacks)
- const nowSeconds = Math.floor(Date.now() / 1000);
- const timestampAge = Math.abs(nowSeconds - responseData.timestamp);
- if (timestampAge > 300) { // 5 minutes
- return false;
- }
- // Build string to verify: key|ip|valid|plan|expires|features|timestamp
- const verifyString = [
- responseData.license_key || '',
- responseData.server_ip || '',
- String(responseData.valid),
- responseData.plan || '',
- responseData.expires_at || '',
- String(responseData.timestamp)
- ].join('|');
- // Calculate expected HMAC
- const expectedHmac = crypto
- .createHmac('sha256', HMAC_SECRET)
- .update(verifyString)
- .digest('hex');
- // Compare securely (timing-safe)
- try {
- return crypto.timingSafeEqual(
- Buffer.from(responseData.hmac, 'hex'),
- Buffer.from(expectedHmac, 'hex')
- );
- } catch (e) {
- return false;
- }
- }
- // ============================================
- // MAKE HTTP REQUEST TO LICENSE SERVER
- // ============================================
- function makeRequest(endpoint, data) {
- return new Promise((resolve, reject) => {
- const options = {
- hostname: new URL(endpoint, LICENSE_SERVER_URL).hostname,
- port: new URL(endpoint, LICENSE_SERVER_URL).port || (isHttps ? 443 : 80),
- pathname: new URL(endpoint, LICENSE_SERVER_URL).pathname,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Content-Length': Buffer.byteLength(JSON.stringify(data)),
- 'User-Agent': `HPanel/${PANEL_VERSION}`
- },
- timeout: 15000,
- rejectUnauthorized: process.env.NODE_ENV === 'production'
- };
- const isHttps = new URL(endpoint, LICENSE_SERVER_URL).protocol === 'https:';
- const httpModule = isHttps ? https : http;
- const req = httpModule.request(options, (res) => {
- let responseBody = '';
- res.on('data', chunk => responseBody += chunk);
- res.on('end', () => {
- try {
- resolve({
- status: res.statusCode,
- data: JSON.parse(responseBody)
- });
- } catch (e) {
- reject(new Error('Invalid JSON response from license server'));
- }
- });
- });
- req.on('error', reject);
- req.on('timeout', () => {
- req.destroy();
- reject(new Error('Request timeout'));
- });
- req.write(JSON.stringify(data));
- req.end();
- });
- }
- // ============================================
- // LOAD CACHED LICENSE STATE
- // ============================================
- function loadCache() {
- try {
- if (fs.existsSync(CACHE_FILE)) {
- return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
- }
- } catch (e) {
- console.error('[License] Failed to load cache:', e.message);
- }
- return null;
- }
- // ============================================
- // SAVE LICENSE STATE TO CACHE
- // ============================================
- function saveCache(state) {
- try {
- const cacheDir = path.dirname(CACHE_FILE);
- // Create directory if it doesn't exist
- if (!fs.existsSync(cacheDir)) {
- fs.mkdirSync(cacheDir, { recursive: true });
- }
- fs.writeFileSync(CACHE_FILE, JSON.stringify({
- ...state,
- cached_at: new Date().toISOString()
- }, null, 2));
- } catch (e) {
- console.error('[License] Failed to save cache:', e.message);
- }
- }
- // ============================================
- // GET CURRENT USAGE COUNTS FROM DATABASE
- // ============================================
- async function getUsageCounts() {
- try {
- const db = require('./database');
- const accountsResult = await db.query('SELECT COUNT(*) as count FROM users');
- const domainsResult = await db.query('SELECT COUNT(*) as count FROM domains');
- return {
- current_accounts: parseInt(accountsResult.rows[0].count) || 0,
- current_domains: parseInt(domainsResult.rows[0].count) || 0
- };
- } catch (e) {
- console.error('[License] Failed to get usage counts:', e.message);
- return {
- current_accounts: 0,
- current_domains: 0
- };
- }
- }
- // ============================================
- // MAIN LICENSE VERIFICATION FUNCTION
- // ============================================
- async function verifyLicense() {
- const serverIp = await getServerIp();
- const hostname = getHostname();
- const usageCounts = await getUsageCounts();
- // Build request payload
- const payload = {
- server_ip: serverIp,
- hostname: hostname,
- panel_version: PANEL_VERSION,
- current_accounts: usageCounts.current_accounts,
- current_domains: usageCounts.current_domains
- };
- console.log(`[License] Verifying ${serverIp} (accounts: ${usageCounts.current_accounts}, domains: ${usageCounts.current_domains})`);
- try {
- // Make request to license server
- const { status, data } = await makeRequest('/api/license/verify', payload);
- // Verify HMAC signature if secret is configured
- if (HMAC_SECRET && data.hmac) {
- if (!verifyHmac(data)) {
- console.error('[License] HMAC verification failed:', {
- license_key: data.license_key || '',
- server_ip: data.server_ip || '',
- valid: String(data.valid),
- plan: data.plan || '',
- expires_at: data.expires_at || '',
- timestamp: data.timestamp
- });
- console.error('[License] Response may have been tampered with!');
- }
- }
- if (data.valid) {
- // License is valid
- licenseState = {
- valid: true,
- plan: data.plan,
- limits: data.limits || {},
- features: data.features || {},
- expires_at: data.expires_at,
- grace: data.grace || false,
- last_check: new Date().toISOString(),
- last_success: new Date().toISOString(),
- error: null,
- offline_mode: false
- };
- saveCache(licenseState);
- console.log(`[License] ✅ Valid license! Plan: ${data.plan}, Grace: ${data.grace}`);
- } else {
- // License invalid
- licenseState = {
- valid: false,
- plan: null,
- limits: null,
- features: null,
- expires_at: null,
- grace: false,
- last_check: new Date().toISOString(),
- last_success: licenseState.last_success,
- error: data.error || 'License invalid',
- offline_mode: false
- };
- console.warn(`[License] ❌ Invalid — ${data.error}`);
- }
- } catch (e) {
- console.error(`[License] Verification failed (server unreachable): ${e.message}`);
- // Try to use cached license
- const cached = loadCache();
- if (cached && cached.valid) {
- const lastSuccess = new Date(cached.expires_at || cached.last_success);
- const hoursSinceLastCheck = (Date.now() - lastSuccess.getTime()) / (1000 * 60 * 60);
- if (hoursSinceLastCheck < OFFLINE_GRACE_HOURS) {
- // Within grace period - allow offline mode
- licenseState = {
- ...cached,
- last_check: new Date().toISOString(),
- offline_mode: true,
- error: `License server unreachable. Operating in offline mode from cache (${Math.round(hoursSinceLastCheck)}h ago)`
- };
- console.log(`[License] Using cached license (${Math.round(hoursSinceLastCheck)}h old)`);
- } else {
- // Grace period expired
- licenseState = {
- valid: false,
- plan: null,
- limits: null,
- features: null,
- expires_at: null,
- grace: false,
- last_check: new Date().toISOString(),
- last_success: cached.last_success,
- error: `License server unreachable and grace period exceeded (${Math.round(hoursSinceLastCheck)}h)`,
- offline_mode: true
- };
- console.warn(`[License] Grace period exceeded (${Math.round(hoursSinceLastCheck)}h)`);
- }
- } else {
- // No cache available
- licenseState.last_check = new Date().toISOString();
- licenseState.error = 'License server unreachable and no cached license available';
- }
- }
- return licenseState;
- }
- // ============================================
- // ACTIVATE NEW LICENSE KEY
- // ============================================
- async function activateLicense(licenseKey) {
- const ENDPOINT = '/api/license/activate';
- const ERROR_MSG = 'License server unreachable';
- const serverIp = await getServerIp();
- const hostname = getHostname();
- try {
- const payload = {
- license_key: licenseKey,
- server_ip: serverIp,
- hostname: hostname,
- panel_version: PANEL_VERSION
- };
- const { status, data } = await makeRequest(ENDPOINT, payload);
- if (data.success) {
- console.log(`[License] ✅ License activated: ${licenseKey.substring(0, 8)}...`);
- return {
- success: true,
- message: data.message
- };
- } else {
- console.error(`[License] ❌ Activation failed: ${data.error}`);
- return {
- success: false,
- message: data.error
- };
- }
- } catch (e) {
- console.error(`[License] Activation error: ${e.message}`);
- return {
- success: false,
- message: ERROR_MSG
- };
- }
- }
- // ============================================
- // GET CURRENT LICENSE STATE
- // ============================================
- function getLicenseState() {
- return {
- ...licenseState,
- server_ip: cachedPublicIp || null
- };
- }
- // ============================================
- // CHECK IF FEATURE IS ENABLED
- // ============================================
- function hasFeature(featureName) {
- if (!licenseState.valid) return false;
- return licenseState.features && licenseState.features[featureName] === true;
- }
- // ============================================
- // CHECK USAGE LIMIT
- // ============================================
- function checkLimit(limitType, currentValue) {
- const result = {
- allowed: false,
- reason: 'License not valid or no limits defined'
- };
- if (!licenseState.valid || !licenseState.limits) return result;
- const maxAllowed = licenseState.limits[limitType];
- // Unlimited (-1) or undefined
- if (maxAllowed === undefined || maxAllowed === null || maxAllowed === -1) {
- return { allowed: true };
- }
- if (currentValue >= maxAllowed) {
- const limitNames = {
- accounts: 'accounts',
- domains: 'domains'
- };
- const displayName = limitNames[limitType] || limitType;
- return {
- allowed: false,
- reason: `You have reached the maximum number of ${displayName} (${currentValue}/${maxAllowed}). Please remove some items or upgrade your license plan.`
- };
- }
- return {
- allowed: true,
- used: currentValue,
- max: maxAllowed
- };
- }
- // ============================================
- // PERIODIC LICENSE CHECKING
- // ============================================
- let checkInterval = null;
- function startPeriodicCheck() {
- if (checkInterval) clearInterval(checkInterval);
- checkInterval = setInterval(() => {
- verifyLicense().catch(e => {
- console.error('[License] Periodic check error:', e.message);
- });
- }, CHECK_INTERVAL_MS);
- console.log(`[License] Periodic check started (every ${CHECK_INTERVAL_MS / 3600000}h)`);
- }
- function stopPeriodicCheck() {
- if (checkInterval) {
- clearInterval(checkInterval);
- checkInterval = null;
- }
- }
- // ============================================
- // EXPORT PUBLIC API
- // ============================================
- module.exports = {
- verifyLicense,
- activateLicense,
- getLicenseState,
- hasFeature,
- checkLimit,
- startPeriodicCheck,
- stopPeriodicCheck
- };
Advertisement
Add Comment
Please, Sign In to add comment