http://modulesgarden.com * CONTACT -> contact@modulesgarden.com * AUTHOR -> MODULESGARDEN * contact@modulesgarden.com * * * * This software is furnished under a license and may be used and copied * only in accordance with the terms of such license and with the * inclusion of the above copyright notice. This software or any other * copies thereof may not be provided or otherwise made available to any * other person. No title to and ownership of the software is hereby * transferred. * * * ******************************************************************** */ namespace MGModule\DNSManager2\mgLibs\custom\dns\utils; use MGModule\DNSManager2\models\custom\reverse\Repository; use MGModule\DNSManager2\mgLibs\custom\dns\record\Record; use MGModule\DNSManager2\mgLibs\custom\dns\utils\ReverseDNSHelper; use MGModule\DNSManager2\mgLibs\exceptions\RegistrarDNSException; use MGModule\DNSManager2\mgLibs\MySQL as mysql; use MGModule\DNSManager2\mgLibs\MySQL\query; use MGModule\DNSManager2\addon; use MGModule\DNSManager2\mgLibs\custom\manager\ClientHelper; use MGModule\DNSManager2\models\custom\zone\Zone; use MGModule\DNSManager2\models\custom\zone\ZoneTypeEnum; use MGModule\DNSManager2 as main; /** * registrar helper for DNS Management * Please make sure that "mocks" folder is readable
* Please also make sure that "function" folder is writable. It is necessairy to create dynamic files with specific DNS Functions */ class CustomDnsRegistrarHelper { public $domainId = null; public $moduleName = null; private $package = null; /** * Current Module Parametrers for given domain * @var Array */ protected $moduleParams = array(); protected $availableTypes = array( 'A' => array( 'name' => 'A', 'ttl' => 14400 ), 'AAAA' => array( 'name' => 'AAAA', 'ttl' => 14400 ), 'MX' => array( 'name' => 'MX', 'ttl' => 14400 ), 'CNAME' => array( 'name' => 'CNAME', 'ttl' => 14400 ), 'TXT' => array( 'name' => 'SPF', 'ttl' => 14400, 'alias' => 'SPF' ), 'TXT' => array( 'name' => 'TXT', 'ttl' => 14400, 'alias' => 'SPF' ) ); const REGISTRAR_DOMAIN_ID_NOT_EXISTS = 100; const REGISTRAR_ZONE_NOT_FOUND = 101; const REGISTRAR_DOMAIN_DETAILS_NOT_FOUND = 102; const REGISTRAR_DOMAIN_NAME_NOT_FOUND = 103; const REGISTRAR_MODULE_NAME_NOT_FOUND = 104; const REGISTRAR_USER_ID_NOT_EXISTS = 105; static public $MSGS_LIST = array( 100 => "Domain ID cannot be empty. Please provide correct Domain ID in %s", 101 => "Unable to find zone for provided domain. Create a new zone first or contact your administrator", 102 => "Unable to get Domain Details Information in %s", 103 => "No Domain name has been provided for %s", 104 => "No Module Name has been found for %s", 105 => "Unable to get User ID in %s. Probably session has expired", ); public final function __construct($domainId = null) { $this->domainId = $domainId; $this->getPackage(); } public function setDomainId($domainId) { $this->domainId = $domainId; } public function getDNSRecords($domainId) { $params = $this->getModuleParams(); addon::I(FALSE, $params); $helper = new ClientHelper($_SESSION['uid']); $zone = new Zone($this->getZoneIdForDomain($domainId)); $dns = array(); if(!$helper->isZoneOwnedByClient($zone)) { return array( 'error' => main\mgLibs\lang::T('you_cannot_edit_this_zone_because_it_is_not_belongs_to_you') ); } $module = $zone->getModule(); $vars['records'] = $module->getRecords(); $vars['records'] = array_merge($vars['records'], $this->getPTRRecords($zone)); foreach($vars['records'] as &$record) { $record->encode(); if(!isset($this->availableTypes[$record['type']])) { continue; } $priority = null; switch($record['type']) { case 'MX': $address = $record['rdata']->exchange; $priority = $record['rdata']->preference; break; case 'CNAME': $address = $record['rdata']['cname']; break; case 'A': $address = $record['rdata']['address']; break; case 'AAAA': $address = $record['rdata']['address']; break; case 'TXT': $address = $record['rdata']['txtdata']; break; default: } $dns[] = array( "hostname" => $record['name'], "line" => $record['line'], "type" => $record['type'], "address" => $address, "priority" => $priority, "name" => $record['name'], "class" => $record['class'], "ttl" => $record['ttl'], "rdlength" => $record['rdlength'], "rdata" => $record["rdata"] ); } return $dns; } /** * * @param type $record * @return type */ private function convertRecord($record) { //applying additional parameters to match DNS Manager spec $record['ttl'] = $this->availableTypes[trim(strtoupper($record['type']))]['ttl']; $record['name'] = $record['hostname']; if($record['type'] == "MX") { $record['exchange'] = $record['address']; $record['preference'] = (int) $record['priority']; $record['rdata']['exchange'] = $record['address']; $record['rdata']['preference'] = (int) $record['priority']; } if($record['type'] == "CNAME") { $record['rdata']['cname'] = $record['address']; } if($record['type'] == "TXT") { $record['rdata']['txtdata'] = $record['address']; } return $record; } private function transformRecords() { $records = $_POST['dnsrecordhost']; $recordsType = $_POST['dnsrecordtype']; $recordsAddress = $_POST['dnsrecordaddress']; $recordsPriority = $_POST['dnsrecordpriority']; $results = array(); foreach($records as $key => $value) { $results[] = array( 'hostname' => $value, 'name' => $value, 'type' => $recordsType[$key], 'address' => $recordsAddress[$key], 'priority' => $recordsPriority[$key], 'ttl' => $this->availableTypes[trim(strtoupper($recordsType[$key]))]['ttl'], 'rdata' => array( 'hostname' => $value, 'name' => $value, 'type' => $recordsType[$key], 'address' => $recordsAddress[$key], 'priority' => $recordsPriority[$key], 'ttl' => $this->availableTypes[trim(strtoupper($recordsType[$key]))]['ttl'], ), ); } return $results; } /** * This function is used to check if record type which is going to be created is allowed and available * @param String $type Name of the DNS record Type to check if is allowed to create * @see Registrar::availableTypes for available types * @return boolean True is returned is allowed - false otherwise */ private function checkTypeIfAvailable($type = null) { if(empty($type)) { return false; } foreach ($this->availableTypes as $key) { if ($key['name'] == trim(strtoupper($type))) { return true; } } return false; } /** * Function used to save or delete DNS record with provided details * @param type $domainId Id of the domain for whitch records would be deleted or created * @return Array . Void otherwise * @throws \Exception Array with error key is returend in case of failure. in that case an exception is thrown */ public function saveDNSRecords($domainId = null) { if (empty($domainId)) { $domainId = $this->domainId; } $zoneLoggerManager = new main\mgLibs\custom\helpers\ZoneLogger\Manager($_SESSION['uid']); $error = array(); $params = $this->getModuleParams(); addon::I(FALSE, $params); $helper = new ClientHelper($_SESSION['uid']); $zone = new Zone($this->getZoneIdForDomain($domainId)); //getting den for $newDNSCollection = $this->transformRecords(); $oldDNSCollection = $this->getDNSRecords($domainId); $limits = $this->checkPackageLimits($newDNSCollection); if($limits) { throw new \Exception($limits, 0); } if (!$helper->isZoneOwnedByClient($zone)) { throw new \Exception(main\mgLibs\lang::T('you_cannot_edit_this_zone_because_it_is_not_belongs_to_you'), 0); } $module = $zone->getModule(); foreach ($newDNSCollection as $newKey => $newValue) { $newValue = $this->convertRecord($newValue); //if such a key does not exists it could be a new one if (!array_key_exists($newKey, $oldDNSCollection)) { if (empty($newValue['hostname']) && empty($newValue['address'])) { continue; } //if type is not allowd - return error if (!$this->checkTypeIfAvailable($newValue['type'])) { $error[] = $newValue['type'] . ' is not supported. Available types: A, AAAA, MX, CNAME, SPF(txt).'; continue; } try { $record = $this->createRecordFromData($newValue); $module->addRecord($record); $zoneLoggerManager->logAddRecordToZone($zone, $record); } catch(\Exception $ex) { $error[] = $ex->getMessage(); } continue; } //if there are no difference - do nothing if($newValue['hostname'] == $oldDNSCollection[$newKey]['hostname'] && $newValue['address'] == $oldDNSCollection[$newKey]['address'] && ($newValue['priority'] == $oldDNSCollection[$newKey]['priority'] || !is_numeric($newValue['priority']))) { continue; } //if there are discrepencies in a host name or IP address remove old record and add new one //or remove only those in which both hostname and address are empty try { $module->deleteRecord($record = $this->createRecordFromData($oldDNSCollection[$newKey])); $zoneLoggerManager->logRemoveRecordFromZone($record); //remove record and if values was empty - do nothing else if (empty($newValue['hostname']) && empty($newValue['address'])) { continue; } } catch(\Exception $ex) { $error[] = $ex->getMessage(); continue; } try { $module->addRecord($record = $this->createRecordFromData($newValue)); } catch(\Exception $ex) { $error[] = $ex->getMessage(); //rollbacking $module->addRecord($record = $this->createRecordFromData($oldDNSCollection[$newKey])); } $zoneLoggerManager->logAddRecordToZone($zone, $record); } if(!empty($error)) { throw new \Exception(implode(".
\n", $error), 1); } } /** * Function used to create record from form data * @param Array $input Collection of form parameters sent by WHMCS while new DNS record creation * @return MGModule\DNSManager2\mgLibs\custom\dns\record\Record */ private function createRecordFromData($input) { $record = Record::tryToCreateFromArray($input); $record->rdata->setDataFromArray($input['rdata']); return $record; } /** * Function used to get Current Domain ID * @return integer ID of the domain is used */ protected function getDomainId() { return (int)$this->domainId; } private function getPTRRecords($zone) { $out = array(); foreach(Repository::factory()->from($zone->getServer()->id, $zone->name)->get() as $data) { $ptr = new Record(); $ptr->name = ReverseDNSHelper::getLastOctetFromIPv4($data->ip) . '.' . $data->name; $ptr->type = 'PTR'; $ptr->ttl = $data->ttl; $ptr->ip = $data->ip; $ptr->createRDATAObject('PTR'); $ptr->rdata->setFirstProperty(empty($data->sub) ? $data->from : $data->sub . '.' . $data->from); $out[] = $ptr; } return $out; } private function getZoneIdForDomain($domainId = null) { if (empty($domainId)) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_DOMAIN_ID_NOT_EXISTS); } $clientid = $_SESSION['uid']; $details = query::query(" SELECT CONCAT('#', tbldomains.id, ' ', tbldomains.domain) AS group_name, :type `type`, tbldomains.id AS relid, dns_manager2_zone.id AS zone_id, dns_manager2_zone.name AS zone_name, dns_manager2_zone.status AS zone_status FROM tbldomains LEFT JOIN dns_manager2_zone ON dns_manager2_zone.type = :type2 AND dns_manager2_zone.relid = tbldomains.id AND dns_manager2_zone.clientid = :clientid WHERE tbldomains.status IN ('Active') AND tbldomains.id = :domainid AND tbldomains.userid = :clientid2", array( 'type' => ZoneTypeEnum::DOMAIN, 'type2' => ZoneTypeEnum::DOMAIN, 'clientid' => $clientid, 'clientid2' => $clientid, 'domainid' => $domainId ))->fetch(); if(empty($details)) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND], __FUNCTION__), self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND); } if(empty($details['zone_id'])) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_ZONE_NOT_FOUND], __FUNCTION__), self::REGISTRAR_ZONE_NOT_FOUND); } return $details['zone_id']; } /** * This function is used to get Current Module Name from DB * @return String Module Name is returned * @throws RegistrarDNSException An exception is thrown if no domainId has been set or nothign has been found in DB */ public function getModuleName() { if(empty($this->domainId)) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_DOMAIN_ID_NOT_EXISTS); } if(!isset($_SESSION['uid'])) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_USER_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_USER_ID_NOT_EXISTS); } $clientId = $_SESSION['uid']; $details = query::query(" SELECT tbldomains.id AS domainId, tbldomains.registrar AS registrar FROM tbldomains WHERE tbldomains.status IN ('Active') AND tbldomains.id = :domainId AND tbldomains.userid = :clientId", array( 'domainId' => $this->domainId, 'clientId' => $clientId, ))->fetch(); if(empty($details)) { throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND], __FUNCTION__), self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND); } $this->moduleName = $details['registrar']; return $details['registrar']; } /** * This function is used to create dynamic file with DNS mamagement functions in it
* Generasted file is later included so the registrar could recognize a set of function so DNS Management section could be activated * @param String $currentPath Current path location of the DNS Manager hook file * @return String Name of the file to be included is returned. void in case of errors */ public function creatreFunctionsFile($currentPath) { $sourceLocation = $currentPath.DS.'storage'.DS.'registrar'.DS.'mocks'.DS.'DNSMockFunction.txt'; $moduleName = $this->getModuleName(); $locaction = $currentPath.DS.'storage'.DS.'registrar'.DS.'functions'.DS; $file = $locaction . $moduleName . "_DNSFunctions.php"; if(!file_exists($file)) { if(!is_readable($currentPath.DS.'storage'.DS.'registrar'.DS.'mocks'.DS)) { throw new \Exception( main\mgLibs\lang::T('Directory').': '. $currentPath . '/storage/registrar/mocks '. main\mgLibs\lang::T('is_not_readable') ); } $fileContent = str_replace("%module_name%", $moduleName, file_get_contents($sourceLocation)); if(!is_writable($locaction)) { throw new \Exception( main\mgLibs\lang::T('Directory').': '. $locaction. main\mgLibs\lang::T('is_not_writable') ); } file_put_contents($file, $fileContent); } return $file; } /** * This function is used to get Parameters saved for currently used module * @param String $module Name of the module we want to find parameters for * @return Array Collection of parameted is returned * @throws RegistrarDNSException An Exception is thrown in case of Errors */ public function getModuleParams($module = null) { if (empty($module)) { $module = $this->getModuleName(); } if (empty($module)) { throw new RegistrarDNSException(); } $q = query::query( "SELECT * FROM `tblregistrars` WHERE `registrar`= :registrar ", array( 'registrar' => $module ))->fetchAll(); $params = array(); foreach ($q as $row) { $params[$row['setting']] = decrypt($row['value']); } return $params; } public function getTld() { $tld = false; $domain = \MGModule\DNSManager2\models\whmcs\domains\domain::factory($this->domainId); if($domain) { $name = $domain->domain; $pos = mb_stripos($name, '.'); $tld = mb_substr($name, $pos); } return $tld; } public function getPackage() { $tldExtension = $this->getTld(); $tld = main\models\whmcs\domains\pricing\domainpricing::getExtensionId($tldExtension); // id if($tld) { $dnsRegistrar = main\models\custom\package\registrar\Repository::factory() ->byTld($tld)->one(); if($dnsRegistrar) { $this->package = new main\models\custom\package\Package($dnsRegistrar->packageid); } } } public function isPackageActive() { if($this->package) { if($this->package->status == "1" && $this->checkRegistrar()) { return true; } } return false; } public function checkPackageLimits($newDNSCollection) { //if(!$this->isPackageActive()) //{ // main\mgLibs\lang::T(''); //} $counts = $this->countRecords($newDNSCollection); $totalLimit = $this->getPackageTotalRecordsLimit(); if((int)$totalLimit < $counts['all']) { return main\mgLibs\lang::T('recordLimitExceded'); } $recordsLimits = $this->getPackageRecordsLimits(); foreach($counts as $rName => $rCount) { if($rName == 'all') { continue; } if((int)$recordsLimits[$rName] < $rCount) { return main\mgLibs\lang::T('youCannotAddSpecificRecords') .' '.$rName.' '.main\mgLibs\lang::T('limitExceded'); } } } public function countRecords($newDNSCollection) { $counts = array('all' => 0); foreach($newDNSCollection as $record) { if($record['hostname'] !== '') { $counts['all']++; if($counts[$record['type']]) { $counts[$record['type']]++; } else { $counts[$record['type']] = 1; } } } return $counts; } private function getPackageRecordsLimits() { $allowedRecords = main\models\custom\package\setting\Repository::factory() ->byPackageID($this->package->id) ->byKey(main\models\custom\package\setting\PackageSettingEnum::ALLOWED_RECORD_TYPES) ->one(); $result = $allowedRecords ? unserialize($allowedRecords->value) : false; return $result; } private function getPackageTotalRecordsLimit() { $limit = main\models\custom\package\item\Repository::factory() ->byPackageID($this->package->id) ->byType(main\models\custom\package\item\PackageItemTypeEnum::DOMAIN) ->byRelID($this->domainId) ->one(); return $limit->limit; } public function checkRegistrar() { $reg = mysql\query::query("SELECT registrar FROM tbldomains WHERE id = :domainID ", array('domainID' => $this->domainId))->fetchColumn("registrar"); $path = str_replace( DS.'addons'.DS.'DNSManager2'.DS.'mgLibs'.DS.'custom'.DS.'dns'.DS.'utils', DS.'registrars'.DS.$reg.DS.$reg.'.php', __DIR__ ); if(file_exists($path)) { if (!function_exists("getRegistrarConfigOptions")) { require ROOTDIR . "/includes/registrarfunctions.php"; } require_once $path; if(!function_exists($reg.'_GetDNS') && !function_exists($reg.'_SaveDNS')) { return true; } } return false; } }