PowerDNSV4.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. <?php
  2. namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
  3. use Exception;
  4. use \MGModule\DNSManager2\mgLibs\custom\dns;
  5. use MGModule\DNSManager2\mgLibs\custom\dns\dnssec\DnsSec;
  6. use MGModule\DNSManager2\mgLibs\custom\dns\submodules\PowerDNSv4\RRSet;
  7. use MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;
  8. use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
  9. use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
  10. use \MGModule\DNSManager2\mgLibs\custom\dns\record\Record;
  11. use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
  12. class PowerDNSV4 extends dns\SubmoduleAbstract implements
  13. interfaces\SubmoduleTTLInterface, interfaces\SubmoduleImportInterface, interfaces\SubmoduleRDNSInterface, interfaces\SubmoduleDNSSecInterface
  14. {
  15. const ERR_WHILE_REMOVING = 'errRemovingRecord';
  16. const ERR_WHILE_CREATING = 'errCreatingRecord';
  17. public $configFields = [
  18. 'hostname' => [
  19. 'friendlyName' => 'Hostname/IP',
  20. 'validators' => [
  21. 'required' => 'required',
  22. ]
  23. ],
  24. 'port' => [
  25. 'friendlyName' => 'Default Port',
  26. 'value' => '8081'
  27. ],
  28. 'ssl' => [
  29. 'friendlyName' => 'SSL',
  30. 'type' => 'yesno',
  31. ],
  32. 'apikey' => [
  33. 'friendlyName' => 'API Key',
  34. 'validators' => [
  35. 'required' => 'required',
  36. ]
  37. ],
  38. 'domain_type' => [
  39. 'friendlyName' => 'Domain Type',
  40. 'type' => 'select',
  41. 'options' => ['MASTER' => 'MASTER', 'SLAVE' => 'SLAVE', 'NATIVE' => 'NATIVE'],
  42. ],
  43. 'notify_slaves' => [
  44. 'friendlyName' => 'Notify Slaves',
  45. 'type' => 'yesno',
  46. ],
  47. 'zone_account' => [
  48. 'friendlyName' => 'Zone Account',
  49. ],
  50. 'increment_soa_serial'=>[
  51. 'friendlyName' => 'Manually Increment SOA Serial',
  52. 'type' => 'yesno',
  53. ],
  54. 'default_ip' => [
  55. 'friendlyName' => 'Default IP',
  56. 'validators' => [
  57. 'required' => 'required',
  58. 'pattern' => Patterns::IP4_OR_IP6,
  59. ]
  60. ],
  61. ];
  62. public $availableTypes = ['MINFO', 'A', 'AAAA', 'NS', 'MX', 'CNAME', 'TXT', 'SRV', 'DNAME', 'SPF', 'PTR', 'ALIAS', 'AFSDB', 'HINFO', 'LOC', 'NAPTR', 'RP', 'CAA', 'SOA', 'DNSKEY', 'DS', 'RRSIG', 'URI', 'TLSA', 'SMIMEA', 'CERT', 'SSHFP', 'NSEC3PARAM', 'NSEC3', 'NSEC'];
  63. private $zoneID = false;
  64. private function get( $function, $params = [], $customRequest = null )
  65. {
  66. $headers[] = 'X-API-Key: ' . $this->config['apikey'];
  67. $port = $this->config['port'] ? : 8081;
  68. $url = $this->config['ssl'] ? 'https' : 'http';
  69. $url .= '://' . $this->config['hostname'] . ':' . $port . '/api/v1/' . $function;
  70. $this->log('REQUEST: ' . $url);
  71. $ch = curl_init();
  72. $chOptions = [
  73. CURLOPT_URL => trim($url),
  74. CURLOPT_RETURNTRANSFER => 1,
  75. CURLOPT_SSL_VERIFYPEER => 0,
  76. CURLOPT_SSL_VERIFYHOST => 0,
  77. CURLOPT_TIMEOUT => 30,
  78. CURLOPT_HTTPHEADER => $headers
  79. ];
  80. if ( $params )
  81. {
  82. $body = json_encode($params);
  83. $body = str_replace(
  84. [
  85. ':{',
  86. '},',
  87. '}}',
  88. '}],{'
  89. ],
  90. [
  91. ':[{',
  92. '}],',
  93. '}]}',
  94. '},{'
  95. ], $body);
  96. $chOptions[CURLOPT_POST] = 1;
  97. $chOptions[CURLOPT_POSTFIELDS] = $body;
  98. }
  99. if ( $customRequest )
  100. {
  101. $chOptions[CURLOPT_CUSTOMREQUEST] = $customRequest;
  102. }
  103. curl_setopt_array($ch, $chOptions);
  104. $out = curl_exec($ch);
  105. $this->log('RESPONSE: ' . $out);
  106. if ( strpos($out, '"error"') !== false)
  107. {
  108. $out = json_decode($out);
  109. throw new exceptions\DNSSubmoduleException(str_replace('\032', ' ', $out->error), dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  110. }
  111. if ( strpos($out, 'Unauthorized') !== false )
  112. {
  113. throw new exceptions\DNSSubmoduleException('Incorrect API Key', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  114. }
  115. if ( curl_errno($ch) )
  116. {
  117. throw new exceptions\DNSSubmoduleException('cURL Error: ' . curl_errno($ch) . ' - ' . curl_error($ch), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  118. }
  119. $info = curl_getinfo($ch);
  120. if($info['http_code'] >= 300)
  121. {
  122. throw new exceptions\DNSSubmoduleException($this->parseHttpCodeError($info['http_code']), $info['http_code']);
  123. }
  124. curl_close($ch);
  125. return $out;
  126. }
  127. private function parseHttpCodeError($code)
  128. {
  129. switch($code)
  130. {
  131. case 400:
  132. $error = 'Bad request to the API';
  133. break;
  134. case 404:
  135. $error = 'API not enabled or invalid URL';
  136. break;
  137. default:
  138. $error = 'The server returned '.$code.' error';
  139. }
  140. return $error;
  141. }
  142. public function testConnection()
  143. {
  144. $out = $this->get('servers');
  145. }
  146. private function getServerID()
  147. {
  148. $out = json_decode($this->get('servers'));
  149. return $out[0]->id;
  150. }
  151. public function getZoneID()
  152. {
  153. if ( $this->zoneID !== false )
  154. {
  155. return $this->zoneID;
  156. }
  157. $serverId = $this->getServerID();
  158. try
  159. {
  160. $zone = json_decode($this->get("servers/$serverId/zones/" . $this->domain, []));
  161. }
  162. catch (exceptions\DNSSubmoduleException $e)
  163. {
  164. if($e->getCode() == 404)
  165. {
  166. throw new exceptions\DNSSubmoduleException('Zone does not exists', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  167. }
  168. }
  169. if ( isset($zone->id) )
  170. {
  171. $this->zoneID = (string)$zone->id;
  172. return (string)$zone->id;
  173. }
  174. throw new exceptions\DNSSubmoduleException('Zone does not exists', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  175. }
  176. public function zoneExists()
  177. {
  178. try
  179. {
  180. $this->getZoneID();
  181. return true;
  182. }
  183. catch (exceptions\DNSSubmoduleException $e)
  184. {
  185. if ( $e->getCode() === dns\SubmoduleExceptionCodes::INVALID_PARAMETERS )
  186. {
  187. return false;
  188. }
  189. throw $e;
  190. }
  191. }
  192. public function getRecords( $recordType = false )
  193. {
  194. $serverId = $this->getServerID();
  195. $zoneId = $this->getZoneID();
  196. $rrsets = json_decode($this->get("servers/$serverId/zones/$zoneId", []), false)->rrsets;
  197. $out = [];
  198. $rrsets = array_filter($rrsets, [self::class, 'filterBrokenRRSets']);
  199. /** @var dns\submodules\PowerDNSv4\RRSet $rrset */
  200. foreach ( $rrsets as $rrset )
  201. {
  202. $type = $rrset->type;
  203. $name = $rrset->name;
  204. $ttl = $rrset->ttl;
  205. $recordClass = 'MGModule\\DNSManager2\\mgLibs\\custom\\dns\\record\\type\\' . $type;
  206. $recordAdapterClass = 'MGModule\\DNSManager2\\mgLibs\\custom\\dns\\submodules\\PowerDNSv4\\Adapters\\' . $type . 'Adapter';
  207. if ( ($recordType && $recordType !== $type) || !class_exists($recordClass) || !in_array($type, $this->availableTypes, true) ) continue;
  208. /** @var dns\submodules\PowerDNSv4\RecordFromServer $recordFromServer */
  209. foreach ( $rrset->records as $recordId => $recordFromServer )
  210. {
  211. if ( class_exists($recordAdapterClass) && method_exists($recordAdapterClass, 'createRdata') )
  212. {
  213. /** @var dns\submodules\PowerDNSv4\Adapters\AbstractPowerDNSv4Adapter $record */
  214. $record = new $recordAdapterClass();
  215. $record->name = IdnaHelper::idnaEncode($name);
  216. $record->type = $type;
  217. $record->line = implode('|', [$name, $type, $recordId]);
  218. $record->ttl = $ttl;
  219. $record->class = 'IN';
  220. $record->createRdata(IdnaHelper::idnaDecode($recordFromServer->content));
  221. }
  222. else
  223. {
  224. $record = new Record();
  225. $record->name = IdnaHelper::idnaEncode($name);
  226. $record->type = $type;
  227. $record->line = implode('|', [$name, $type, $recordId]);
  228. $record->ttl = $ttl;
  229. $record->class = 'IN';
  230. $additionalProps = explode(' ', IdnaHelper::idnaDecode($recordFromServer->content));
  231. $record->rdata = new $recordClass();
  232. foreach ( array_keys(get_object_vars($record->rdata)) as $index => $additionalProperty )
  233. {
  234. $record->rdata->$additionalProperty = $additionalProps[$index];
  235. }
  236. }
  237. $out[] = $record;
  238. }
  239. }
  240. usort($out, static function ( $a, $b )
  241. {
  242. /** @var Record $a */
  243. /** @var Record $b */
  244. $recordAllignment = ['SOA', 'A', 'AAAA', 'NS', 'MX', 'TXT'];
  245. if ( !in_array($a->type, $recordAllignment, true) && !in_array($b->type, $recordAllignment, true) ) return 0;
  246. if ( !in_array($a->type, $recordAllignment, true) ) return 1;
  247. if ( !in_array($b->type, $recordAllignment, true) ) return -1;
  248. if ( $a->type === $b->type )
  249. {
  250. return $a->name < $b->name ? -1 : 1;
  251. }
  252. return array_search($a->type, $recordAllignment, true) < array_search($b->type, $recordAllignment, true) ? -1 : 1;
  253. });
  254. return $out;
  255. }
  256. /**
  257. * Generates Valid RRSet format for one record ( usefull when we can use replace function )
  258. *
  259. * @param Record $record
  260. * @param string $changetype
  261. *
  262. * @return array
  263. */
  264. private function recordToParamsArray( dns\record\Record $record, $changetype = 'REPLACE' )
  265. {
  266. return [
  267. 'rrsets' => [
  268. 'name' => $record->name,
  269. 'type' => $record->type,
  270. 'ttl' => $record->ttl,
  271. 'changetype' => $changetype,
  272. 'comments' => [],
  273. 'records' => [$this->formatRecordToPowerDnsFormat($record)]
  274. ]
  275. ];
  276. }
  277. /**
  278. * @param Record $record
  279. *
  280. * @throws exceptions\DNSSubmoduleException
  281. */
  282. public function addRecord( dns\record\Record $record )
  283. {
  284. $serverId = $this->getServerID();
  285. $zoneId = $this->getZoneID();
  286. $records = $this->getRecords();
  287. $record->name = IdnaHelper::idnaEncode($record->nameToAbsolute($this->domain));
  288. $records[] = $record;
  289. if( $this->config['increment_soa_serial'] === 'on' )
  290. {
  291. $records = $this->incrementSOASerial($records);
  292. }
  293. $params = $this->recordsToParamArray($records);
  294. $params = $this->replaceTargetRRSetTTL($params, $record);
  295. $this->get("servers/$serverId/zones/$zoneId", $params, 'PATCH');
  296. if ( $this->isSigned() )
  297. {
  298. $this->rectify();
  299. }
  300. $this->checkAndRunNotifySlaves($serverId, $zoneId);
  301. }
  302. public function editRecord( dns\record\Record $record )
  303. {
  304. $serverId = $this->getServerID();
  305. $zoneId = $this->getZoneID();
  306. $record->nameToAbsolute($this->domain);
  307. $record->name = IdnaHelper::idnaEncode($record->name);
  308. $records = $this->getRecords();
  309. $recordsBeforeUpdate = $records;
  310. /** @var Record $records */
  311. foreach ( $records as $index => $recordBeforeUpdate )
  312. {
  313. if ( $recordBeforeUpdate->line === $record->line )
  314. {
  315. $records[$index] = $record;
  316. break;
  317. }
  318. }
  319. if( $this->config['increment_soa_serial'] === 'on' )
  320. {
  321. $records = $this->incrementSOASerial($records);
  322. }
  323. $params = $this->recordsToParamArray($records);
  324. $params = $this->replaceTargetRRSetTTL($params, $record);
  325. $errorType = null;
  326. try
  327. {
  328. $this->removeRRsets($recordsBeforeUpdate);
  329. }
  330. catch( exceptions\DNSSubmoduleException $e )
  331. {
  332. $errorType = self::ERR_WHILE_REMOVING;
  333. }
  334. try
  335. {
  336. $this->get("servers/$serverId/zones/$zoneId", $params, 'PATCH');
  337. }
  338. catch( exceptions\DNSSubmoduleException $e )
  339. {
  340. $errorType = $errorType ? : self::ERR_WHILE_CREATING;
  341. $this->restoreOldRecords($recordsBeforeUpdate,$errorType);
  342. }
  343. if ( $this->isSigned() )
  344. {
  345. $this->rectify();
  346. }
  347. }
  348. public function deleteRecord( dns\record\Record $record )
  349. {
  350. if( $record->type === 'SOA' ) throw new exceptions\DNSSubmoduleException('You are not allowed to delete SOA record');
  351. $serverId = $this->getServerID();
  352. $zoneId = $this->getZoneID();
  353. $record->nameToAbsolute($this->domain);
  354. $record->name = IdnaHelper::idnaEncode($record->name);
  355. $records = $this->getRecords();
  356. $recordsBeforeUpdate = $records;
  357. /** @var Record $records */
  358. foreach ( $records as $index => $recordBeforeUpdate )
  359. {
  360. if ( $recordBeforeUpdate->line === $record->line )
  361. {
  362. unset($records[$index]);
  363. break;
  364. }
  365. }
  366. if( $this->config['increment_soa_serial'] === 'on' )
  367. {
  368. $records = $this->incrementSOASerial($records);
  369. }
  370. $params = $this->recordsToParamArray($records);
  371. $errorType = null;
  372. try
  373. {
  374. $this->removeRRsets($recordsBeforeUpdate);
  375. }
  376. catch( exceptions\DNSSubmoduleException $e )
  377. {
  378. $errorType = self::ERR_WHILE_REMOVING;
  379. }
  380. try
  381. {
  382. $this->get("servers/$serverId/zones/$zoneId", $params, 'PATCH');
  383. }
  384. catch( exceptions\DNSSubmoduleException $e )
  385. {
  386. $errorType = $errorType ? : self::ERR_WHILE_CREATING;
  387. $this->restoreOldRecords($recordsBeforeUpdate, $errorType);
  388. }
  389. if ( $this->isSigned() )
  390. {
  391. $this->rectify();
  392. }
  393. $this->checkAndRunNotifySlaves($serverId, $zoneId);
  394. }
  395. public function activateZone( $dnsZoneName = false )
  396. {
  397. $serverId = $this->getServerID();
  398. $params = [
  399. 'name' => rtrim($this->domain, '.') . '.',
  400. 'kind' => $this->config['domain_type'],
  401. 'nameservers' => [],
  402. ];
  403. if ( !empty($this->config['zone_account']) )
  404. {
  405. $params['account'] = $this->config['zone_account'];
  406. }
  407. if ( $dnsZoneName )
  408. {
  409. $params['name'] = rtrim($dnsZoneName, '.') . '.';
  410. }
  411. return $this->get("servers/$serverId/zones", $params);
  412. }
  413. public function terminateZone()
  414. {
  415. $serverId = $this->getServerID();
  416. $zoneId = $this->getZoneID();
  417. $this->get("servers/$serverId/zones/$zoneId", [], 'DELETE');
  418. }
  419. public function getZones()
  420. {
  421. $serverId = $this->getServerID();
  422. $ret = json_decode($this->get("servers/$serverId/zones", []));
  423. $out = [];
  424. foreach ( $ret as $zone )
  425. {
  426. $masters = null;
  427. if ( !empty($zone->masters) )
  428. {
  429. $masters = implode(', ', $zone->masters);
  430. }
  431. $out[substr((string)$zone->name, 0, -1)] = $masters;
  432. }
  433. return $out;
  434. }
  435. public function updateRDNS( $ip, $ttl = false, $value = false )
  436. {
  437. $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
  438. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  439. $serverId = $this->getServerID();
  440. if ( !$zoneId )
  441. {
  442. $this->activateZone($revDnsZoneName);
  443. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  444. }
  445. //We don't allow to replace name of the rdns record so we can simply use patch
  446. $revRecord = $this->createPowerDnsPTRRecord($ip, $ttl, $value, $revDnsZoneName);
  447. $this->get("servers/$serverId/zones/$zoneId", $this->recordToParamsArray($revRecord), 'PATCH');
  448. $this->checkAndRunNotifySlaves($serverId, $zoneId);
  449. }
  450. private function createPowerDnsPTRRecord( $ip, $ttl, $value, $dnsZoneName )
  451. {
  452. $record = new Record();
  453. $record->name = $this->getLastPartOfIp($ip, true) . rtrim($dnsZoneName, '.') . '.';
  454. $record->ttl = $ttl;
  455. $record->type = 'PTR';
  456. $record->rdata = new dns\record\type\PTR();
  457. $record->rdata->ptrdname = $value;
  458. return $record;
  459. }
  460. public function removeRDNS( $ip )
  461. {
  462. $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
  463. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  464. if ( !$zoneId )
  465. {
  466. return false;
  467. }
  468. $record = $this->getRDNSRecord($ip);
  469. if ( $record === false )
  470. {
  471. return false;
  472. }
  473. $clonedZone = $this->getClone($ip);
  474. $clonedZone->deleteRecord($record);
  475. return true;
  476. }
  477. private function getLastPartOfIp( $ip, $withDot = false )
  478. {
  479. $ipLastPart = dns\utils\ReverseDNSHelper::reverseRecordName($ip);
  480. return $withDot ? $ipLastPart . '.' : $ipLastPart;
  481. }
  482. public function getRDNSRecord( $ip )
  483. {
  484. $clonedZone = $this->getClone($ip);
  485. $records = $clonedZone->getRecords('PTR');
  486. $revZoneName = $this->getLastPartOfIp($ip, true) . rtrim(dns\utils\ReverseDNSHelper::reverseZoneName($ip), '.') . '.';
  487. foreach ( $records as $record )
  488. {
  489. if ( $record->name === $revZoneName ) return $record;
  490. }
  491. return false;
  492. }
  493. private function getRevDNSZoneID( $zoneName )
  494. {
  495. $zoneName = rtrim($zoneName, '.') . '.';
  496. $serverId = $this->getServerID();
  497. $out = json_decode($this->get("servers/$serverId/zones", []));
  498. foreach ( $out as $zone )
  499. {
  500. if ( $zone->name === $zoneName )
  501. {
  502. $this->zoneID = (string)$zone->id;
  503. return (string)$zone->id;
  504. }
  505. }
  506. return false;
  507. }
  508. private function checkAndRunNotifySlaves( $serverID = null, $zoneID = null )
  509. {
  510. if ( $serverID !== null && $zoneID !== null && $this->config['notify_slaves'] === 'on' )
  511. {
  512. $this->get('servers/' . $serverID . '/zones/' . $zoneID . '/notify', [], 'PUT');
  513. }
  514. }
  515. //DNS SEC
  516. public function getSignKeys()
  517. {
  518. $dnssec = new \MGModule\DNSManager2\mgLibs\custom\dns\dnssec\DnsSec();
  519. $serverId = $this->getServerID();
  520. $zoneId = $this->getZoneID();
  521. $ret = json_decode($this->get("servers/$serverId/zones/$zoneId/cryptokeys", []));
  522. foreach ( $ret as $record )
  523. {
  524. foreach ( $record->ds as $dsRecord )
  525. {
  526. $ex = explode(' ', $dsRecord);
  527. $ds = new dns\record\type\DS();
  528. $ds->setKeytag($ex[0]);
  529. $ds->setAlgorithm($ex[1]);
  530. $ds->setDigestType($ex[2]);
  531. $ds->setDigest($ex[3]);
  532. $dnssec->addDs($ds);
  533. }
  534. switch ( strtolower($record->keytype) )
  535. {
  536. case 'csk':
  537. $dnsKeyEx = explode(' ', $record->dnskey);
  538. $dnskey = new dns\record\type\DNSKEY();
  539. $dnskey->setFlags($dnsKeyEx[0]);
  540. $dnskey->setProtocol($dnsKeyEx[1]);
  541. $dnskey->setAlgorithm($dnsKeyEx[2]);
  542. $dnskey->setPublicKey($dnsKeyEx[3]);
  543. $zoneKey = new dns\dnssec\CSK();
  544. $zoneKey->setId($record->id);
  545. $zoneKey->setBits($record->bits);
  546. $zoneKey->setDnsKey($dnskey);
  547. $dnssec->addKey($zoneKey);
  548. break;
  549. case 'ksk':
  550. case 'zsk':
  551. $dnsKeyEx = explode(' ', $record->dnskey);
  552. $dnskey = new dns\record\type\DNSKEY();
  553. $dnskey->setFlags($dnsKeyEx[0]);
  554. $dnskey->setProtocol($dnsKeyEx[1]);
  555. $dnskey->setAlgorithm($dnsKeyEx[2]);
  556. $dnskey->setPublicKey($dnsKeyEx[3]);
  557. $dnskey2 = new dns\record\type\DNSKEY();
  558. $dnskey2->setFlags($dnsKeyEx[0]);
  559. $dnskey2->setProtocol($dnsKeyEx[1]);
  560. $dnskey2->setAlgorithm($dnsKeyEx[2]);
  561. $ksk = new dns\dnssec\KSK();
  562. $ksk->setId($record->id);
  563. $ksk->setBits($record->bits);
  564. $ksk->setDnsKey($dnskey);
  565. $zsk = new dns\dnssec\ZSK();
  566. $zsk->setId($record->id);
  567. $zsk->setBits($record->bits);
  568. $zsk->setDnsKey($dnskey2);
  569. $dnssec->addKey($ksk);
  570. $dnssec->addKey($zsk);
  571. break;
  572. }
  573. }
  574. return $dnssec;
  575. }
  576. /**
  577. * Sign DNS zone
  578. */
  579. public function sign()
  580. {
  581. $this->changeStatus(true);
  582. }
  583. /**
  584. * Unsign DNS zone
  585. */
  586. public function unsign()
  587. {
  588. $this->changeStatus(false);
  589. }
  590. private function changeStatus( $status = true, $dnsZoneName = null )
  591. {
  592. $serverId = $this->getServerID();
  593. $zoneId = $this->getZoneID();
  594. $params = [
  595. 'dnssec' => (boolean)$status,
  596. ];
  597. if ( $dnsZoneName )
  598. {
  599. $params['name'] = rtrim($dnsZoneName, '.') . '.';
  600. }
  601. $this->get("servers/$serverId/zones/$zoneId", $params, 'PUT');
  602. }
  603. /**
  604. * Rectify DNS zone
  605. */
  606. public function rectify()
  607. {
  608. $serverId = $this->getServerID();
  609. $zoneId = $this->getZoneID();
  610. $this->get("servers/$serverId/zones/$zoneId/rectify", [], 'PUT');
  611. }
  612. /**
  613. *
  614. */
  615. public function isSigned()
  616. {
  617. try
  618. {
  619. $serverId = $this->getServerID();
  620. $zoneId = $this->getZoneID();
  621. return json_decode($this->get("servers/$serverId/zones/$zoneId", []))->dnssec;
  622. }
  623. catch ( Exception $ex)
  624. {
  625. return false;
  626. }
  627. }
  628. /**
  629. * Creates rrsets structure from Records array
  630. * @param array $records
  631. * @param string $changeType
  632. * @return mixed
  633. */
  634. private function recordsToParamArray( $records, $changeType = 'REPLACE')
  635. {
  636. $names = $this->getUniqueNames($records);
  637. $types = $this->getUniqueTypes($records);
  638. if( $changeType === 'DELETE' )
  639. {
  640. $types = array_diff($types, ['SOA']);
  641. }
  642. $out = ['rrsets' => []];
  643. foreach ( $names as $name )
  644. {
  645. foreach ( $types as $type )
  646. {
  647. $rrset = new RRSet();
  648. $rrset->name = $name;
  649. $rrset->type = $type;
  650. $rrset->ttl = $this->getRRSetTTL($name, $type, $records);
  651. $rrset->changetype = $changeType;
  652. $rrset->comments = [];
  653. $rrset->records = $this->getRRsetForNameAndType($name, $type, $records);
  654. if ( $rrset->records )
  655. {
  656. $out['rrsets'][] = $rrset;
  657. }
  658. }
  659. }
  660. return $out;
  661. }
  662. /**
  663. * @param array $records
  664. * @return array
  665. */
  666. private function getUniqueNames( $records )
  667. {
  668. $names = [];
  669. /** @var Record $record */
  670. foreach ( $records as $record )
  671. {
  672. $names[] = $record->name;
  673. }
  674. return array_unique($names);
  675. }
  676. /**
  677. * @param array $records
  678. * @return array
  679. */
  680. private function getUniqueTypes( $records )
  681. {
  682. $types = [];
  683. /** @var Record $record */
  684. foreach ( $records as $record )
  685. {
  686. $types[] = $record->type;
  687. }
  688. return array_unique($types);
  689. }
  690. /**
  691. * @param $name
  692. * @param $type
  693. * @param $records
  694. * @return array
  695. */
  696. private function getRRsetForNameAndType( $name, $type, $records )
  697. {
  698. $out = [];
  699. foreach ( $records as $record )
  700. {
  701. if ( $record->name === $name && $record->type === $type )
  702. {
  703. $out[] = $this->formatRecordToPowerDnsFormat($record);
  704. }
  705. }
  706. return $out;
  707. }
  708. /**
  709. * @param Record $record
  710. * @return array
  711. */
  712. private function formatRecordToPowerDnsFormat( Record $record )
  713. {
  714. $recordAdapterClass = 'MGModule\\DNSManager2\\mgLibs\\custom\\dns\\submodules\\PowerDNSv4\\Adapters\\' . $record->type . 'Adapter';
  715. if ( class_exists($recordAdapterClass) && method_exists($recordAdapterClass, 'parseContentToApiFormat') )
  716. {
  717. $content = (new $recordAdapterClass)->parseContentToApiFormat($record->rdata);
  718. }
  719. else
  720. {
  721. $content = str_replace("\t",' ',$record->rdata->toString());
  722. }
  723. return ['content' => $content, 'disabled' => false];
  724. }
  725. /**
  726. * Function removes all RRsets from server
  727. * It's easier when we remove all and then add all since we can't update single record cuz REPLACE duplicates record
  728. * @param $records
  729. * @return bool
  730. * @throws exceptions\DNSSubmoduleException
  731. */
  732. private function removeRRsets( $records )
  733. {
  734. $serverId = $this->getServerID();
  735. $zoneId = $this->getZoneID();
  736. $removeParams = $this->recordsToParamArray($records, 'DELETE');
  737. $this->get("servers/$serverId/zones/$zoneId", $removeParams, 'PATCH');
  738. if( count($this->getRecords()) )
  739. {
  740. throw new exceptions\DNSSubmoduleException(self::ERR_WHILE_REMOVING);
  741. }
  742. return true;
  743. }
  744. /**
  745. * @param string $name
  746. * @param string $type
  747. * @param array $records
  748. *
  749. * @return int
  750. */
  751. private function getRRSetTTL( $name, $type, array $records )
  752. {
  753. foreach( $records as $record )
  754. {
  755. if( $record->type === $type && $record->name === $name )
  756. {
  757. return $record->ttl;
  758. }
  759. }
  760. return 3600;
  761. }
  762. /**
  763. * @param array $params
  764. * @param Record $record
  765. *
  766. * @return mixed
  767. */
  768. private function replaceTargetRRSetTTL( $params, Record $record )
  769. {
  770. /*** @var RRSet $rrset */
  771. foreach( $params['rrsets'] as $index => $rrset )
  772. {
  773. if( $rrset->name === $record->name && $rrset->type === $record->type )
  774. {
  775. $params['rrsets'][$index]->ttl = $record->ttl ?: 3600;
  776. return $params;
  777. }
  778. }
  779. return $params;
  780. }
  781. /**
  782. * @param array $oldRecords
  783. * @param string $errorType
  784. *
  785. * @throws exceptions\DNSSubmoduleException
  786. */
  787. private function restoreOldRecords( $oldRecords, $errorType = self::ERR_WHILE_CREATING )
  788. {
  789. $serverId = $this->getServerID();
  790. $zoneId = $this->getZoneID();
  791. $errors = [];
  792. foreach( $oldRecords as $oldRecord )
  793. {
  794. try
  795. {
  796. $this->get("servers/$serverId/zones/$zoneId", $this->recordToParamsArray($oldRecord), 'PATCH');
  797. }
  798. catch( exceptions\DNSSubmoduleException $e )
  799. {
  800. $errors[] = $e->getMessage();
  801. }
  802. }
  803. if( count($errors) )
  804. {
  805. $msg = $errorType === self::ERR_WHILE_REMOVING ? 'Zone corrupted contact administrator. Affected records: ' : 'Couldn\'t create records: ';
  806. throw new exceptions\DNSSubmoduleException($msg . implode('<br />', $errors));
  807. }
  808. }
  809. /**
  810. * @param RRSet $rrset
  811. *
  812. * @return bool
  813. */
  814. private function filterBrokenRRSets( $rrset )
  815. {
  816. $domainToCheck = rtrim(IdnaHelper::idnaEncode($this->domain), ' .') . '.';
  817. return (bool)preg_match('/' . preg_quote($domainToCheck, '/') . '$/', $rrset->name);
  818. }
  819. /**
  820. * @param array $records
  821. *
  822. * @return array
  823. */
  824. public function incrementSOASerial( array $records )
  825. {
  826. /** @var Record $record */
  827. foreach( $records as &$record )
  828. {
  829. if( $record->type === 'SOA' )
  830. {
  831. $record->rdata->serial++;
  832. return $records;
  833. }
  834. }
  835. }
  836. }