<?php
namespace WHMCS;
class License
{
const LICENSE_API_VERSION = '1.1';
const LICENSE_API_HOSTS = array('127.0.0.1');
const STAGING_LICENSE_API_HOSTS = array('127.0.0.1');
const UNLICENSED_KEY = 'LICENSE-REQUIRED';
private $licensekey = '';
private $localkey = false;
private $keydata = NULL;
private $salt = '';
private $postmd5hash = '';
private $localkeydays = '10';
private $allowcheckfaildays = '5';
private $useInternalLicensingMirror = false;
private $debuglog = array();
private $lastCurlError = NULL;
public function checkFile($value)
{
if ($value != 'a896faf2c31f2acd47b0eda0b3fd6070958f1161') {
throw new Exception\Fatal('File version mismatch. Please contact support.');
}
return $this;
}
public function setLicenseKey($licenseKey)
{
$this->licensekey = $licenseKey;
return $this;
}
public function setLocalKey($localKey)
{
$this->decodeLocal($localKey);
return $this;
}
public function setSalt($version, $hash)
{
if (empty($version) || empty($hash)) {
throw new Exception('Unable to generate licensing salt');
}
$this->salt = sha1(sprintf('WHMCS%s%s%s', $version, '|-|', $hash));
return $this;
}
public function useInternalValidationMirror()
{
$this->useInternalLicensingMirror = true;
return $this;
}
protected function getHosts()
{
if ($this->useInternalLicensingMirror) {
return self::STAGING_LICENSE_API_HOSTS;
}
return self::LICENSE_API_HOSTS;
}
public function getLicenseKey()
{
return $this->licensekey;
}
protected function getHostDomain()
{
$domain = defined('WHMCS_LICENSE_DOMAIN') ? WHMCS_LICENSE_DOMAIN : '';
if (empty($domain) || $domain == '-') {
throw new Exception('Unable to retrieve current server name. Please check PHP/vhost configuration and ensure SERVER_NAME is displaying appropriately via PHP Info.');
}
$this->debug('Host Domain: ' . $domain);
return $domain;
}
protected function getHostIP()
{
$ip = defined('WHMCS_LICENSE_IP') ? WHMCS_LICENSE_IP : '';
$this->debug('Host IP: ' . $ip);
return $ip;
}
protected function getHostDir()
{
$directory = defined('WHMCS_LICENSE_DIR') ? WHMCS_LICENSE_DIR : '';
$this->debug('Host Directory: ' . $directory);
return $directory;
}
private function getSalt()
{
return $this->salt;
}
protected function isLocalKeyValidToUse()
{
$licenseKey = $this->getKeyData('key');
if (empty($licenseKey) || $licenseKey != $this->licensekey) {
throw new Exception('License Key Mismatch in Local Key');
}
$originalcheckdate = $this->getCheckDate();
$localmax = Carbon::now()->startOfDay()->addDays(2);
if ($originalcheckdate->gt($localmax)) {
throw new Exception('Original check date is in the future');
}
}
protected function hasLocalKeyExpired()
{
$originalCheckDate = $this->getCheckDate();
$localExpiryMax = Carbon::now()->startOfDay()->subDays($this->localkeydays);
if (!$originalCheckDate || $originalCheckDate->lt($localExpiryMax)) {
throw new Exception('Original check date is outside allowed validity period');
}
}
protected function buildPostData()
{
$whmcs = \DI::make('app');
$stats = json_decode($whmcs->get_config('SystemStatsCache'), true);
if (!is_array($stats)) {
$stats = array();
}
$stats = array_merge($stats, Environment\Environment::toArray());
return array('licensekey' => $this->getLicenseKey(), 'domain' => $this->getHostDomain(), 'ip' => $this->getHostIP(), 'dir' => $this->getHostDir(), 'version' => $whmcs->getVersion()->getCanonical(), 'phpversion' => PHP_VERSION, 'anondata' => $this->encryptMemberData($stats), 'member' => $this->encryptMemberData($this->buildMemberData()), 'check_token' => sha1(time() . $this->getLicenseKey() . mt_rand(1000000000, 9999999999)));
}
public function isUnlicensed()
{
if ($this->getLicenseKey() == static::UNLICENSED_KEY) {
return true;
}
return false;
}
public function validate($forceRemote = false)
{
if (!$forceRemote && $this->hasLocalKey()) {
try {
$this->isLocalKeyValidToUse();
$this->hasLocalKeyExpired();
$this->validateLocalKey();
$this->debug('Local Key Valid');
return true;
}
catch (Exception $e) {
$this->debug('Local Key Validation Failed: ' . $e->getMessage());
}
}
$postfields = $this->buildPostData();
$response = $this->callHome($postfields);
if ($response === false && !is_null($this->lastCurlError)) {
$this->debug('CURL Error: ' . $this->lastCurlError);
}
if (!Environment\Php::isFunctionAvailable('base64_decode')) {
throw new Exception('Required function base64_decode is not available');
}
if ($response) {
try {
$results = $this->processResponse($response);
if ($results['hash'] != sha1('WHMCSV5.2SYH' . $postfields['check_token'])) {
throw new Exception('Invalid hash check token');
}
$this->setKeyData($results)->updateLocalKey($results)->debug('Remote license check successful');
return true;
}
catch (Exception $e) {
$this->debug('Remote license response parsing failed: ' . $e->getMessage());
}
}
$this->debug('Remote license check failed. Attempting local key fallback.');
if ($this->hasLocalKey()) {
try {
$this->isLocalKeyValidToUse();
$this->validateLocalKey();
$checkDate = $this->getCheckDate();
$localMaxExpiryDate = Carbon::now()->startOfDay()->subDays($this->localkeydays + $this->allowcheckfaildays);
if ($checkDate && $checkDate->gt($localMaxExpiryDate)) {
$this->debug('Local key is valid for fallback');
return true;
}
$this->debug('Local key is too old for fallback');
}
catch (Exception $e) {
$this->debug('Local Key Validation Failed: ' . $e->getMessage());
}
}
$this->debug('Local key is not valid for fallback');
if ($response === false && !is_null($this->lastCurlError)) {
throw new Exception('CURL Error: ' . $this->lastCurlError);
}
throw new Exception\Http\ConnectionError();
}
private function callHomeLoop($query_string, $timeout = 5)
{
foreach ($this->getHosts() as $host) {
try {
$this->debug('Attempting call home with host: ' . $host);
return $this->makeCall($this->getVerifyUrl($host), $query_string, $timeout);
}
catch (Exception $e) {
$this->debug('Remote call failed: ' . $e->getMessage());
}
}
return false;
}
protected function callHome($postfields)
{
$this->validateCurlIsAvailable();
$query_string = build_query_string($postfields);
$response = $this->callHomeLoop($query_string, 5);
if ($response) {
return $response;
}
return $this->callHomeLoop($query_string, 30);
}
private function getVerifyUrl($host)
{
return 'https://' . $host . '/1.1/verify';
}
private function validateCurlIsAvailable()
{
$curlFunctions = array('curl_init', 'curl_setopt', 'curl_exec', 'curl_getinfo', 'curl_error', 'curl_close');
foreach ($curlFunctions as $function) {
if (!Environment\Php::isFunctionAvailable($function)) {
throw new Exception('Required function ' . $function . ' is not available');
}
}
}
protected function makeCall($url, $query_string, $timeout = 5)
{
/*
$this->debug('Timeout ' . $timeout);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->useInternalLicensingMirror ? 0 : 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->useInternalLicensingMirror ? 0 : 1);
$response = curl_exec($ch);
$responsecode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_error($ch)) {
$this->lastC