Bind9.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  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\exceptions\DNSSubmoduleException;
  6. use MGModule\DNSManager2\mgLibs\custom\dns\exceptions\DNSSubmoduleHiddenException;
  7. use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
  8. use MGModule\DNSManager2\mgLibs\custom\dns\SubmoduleExceptionCodes;
  9. use MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;
  10. use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
  11. use MGModule\DNSManager2\mgLibs\custom\helpers\TimeUnitsAliassesHelper;
  12. use phpseclib\Crypt\RSA;
  13. use phpseclib\Net\SFTP;
  14. use phpseclib\Net\SSH2;
  15. /**
  16. * Class Bind9 support for Bind9 server
  17. * @package MGModule\DNSManager2\mgLibs\custom\dns\submodules
  18. */
  19. class Bind9 extends dns\SubmoduleAbstract implements
  20. interfaces\SubmoduleImportInterface,
  21. interfaces\SubmoduleTTLInterface
  22. {
  23. public $configFields = [
  24. 'hostname' => [
  25. 'friendlyName' => 'Hostname/IP',
  26. 'placeholder' => 'example.com',
  27. 'validators' => [
  28. 'required' => 'required',
  29. ]
  30. ],
  31. 'username' => [
  32. 'friendlyName' => 'Username',
  33. 'validators' => [
  34. 'required' => 'required',
  35. ]
  36. ],
  37. 'password' => [
  38. 'friendlyName' => 'User Password',
  39. 'type' => 'password',
  40. 'validators' => [
  41. 'required' => 'required'
  42. ],
  43. ],
  44. 'default_ip' => [
  45. 'friendlyName' => 'Default IP',
  46. 'placeholder' => '10.10.10.10',
  47. 'validators' => [
  48. 'required' => 'required',
  49. 'pattern' => Patterns::IP4_OR_IP6,
  50. ]
  51. ],
  52. 'rsa' => [
  53. 'friendlyName' => 'RSA Private Key',
  54. 'type' => 'textarea',
  55. ],
  56. 'ssl' => [
  57. 'friendlyName' => 'Enable SSL',
  58. 'type' => 'yesno',
  59. ],
  60. 'pathtoconfig' => [
  61. 'friendlyName' => 'Path To Bind9 Config Directory',
  62. 'placeholder' => '/etc/bind/',
  63. 'validators' => [
  64. 'required' => 'required',
  65. ]
  66. ],
  67. 'pathtobackup' => [
  68. 'friendlyName' => 'Path To Backup Directory',
  69. 'placeholder' => '/etc/bind/backups/',
  70. 'validators' => [
  71. 'required' => 'required',
  72. ]
  73. ],
  74. 'rname' => [
  75. 'friendlyName' => 'Admin Email (RNAME)',
  76. 'placeholder' => 'admin.example.com.',
  77. 'validators' => [
  78. 'required' => 'required'
  79. ],
  80. ],
  81. 'refresh' => [
  82. 'friendlyName' => 'Refresh',
  83. 'placeholder' => '900',
  84. 'validators' => [
  85. 'required' => 'required'
  86. ],
  87. ],
  88. 'retry' => [
  89. 'friendlyName' => 'Retry',
  90. 'placeholder' => '600',
  91. 'validators' => [
  92. 'required' => 'required'
  93. ],
  94. ],
  95. 'expire' => [
  96. 'friendlyName' => 'Expire',
  97. 'placeholder' => '86400',
  98. 'validators' => [
  99. 'required' => 'required'
  100. ],
  101. ],
  102. 'notify_slaves' => [
  103. 'friendlyName' => 'Notify Slaves',
  104. 'type' => 'yesno',
  105. ],
  106. 'master_ip' => [
  107. 'friendlyName' => 'Master IP',
  108. 'placeholder' => '10.10.10.10',
  109. 'help' => 'Required when notify slaves are active'
  110. ],
  111. 'slaves' => [
  112. 'friendlyName' => 'Slaves IP List',
  113. 'type' => 'textarea',
  114. 'placeholder' => '10.10.10.4, 10.10.10.5, 10.10.10.6'
  115. ],
  116. 'customSettings' => [
  117. 'friendlyName' => 'Additional Settings',
  118. 'type' => 'textarea',
  119. 'help' => 'It will be placed in named.conf.local as plain text make sure your format is valid',
  120. 'placeholder' => " max-journal-size 50k;\n min-retry-time 100;"
  121. ]
  122. ];
  123. public $availableTypes = ['A', 'AAAA', 'CAA', 'DS', 'HINFO', 'NS', 'MX', 'CNAME', 'DNAME', 'TXT', 'SRV', 'AFSDB', 'NAPTR', 'RP', 'SOA'];
  124. private $soa = [];
  125. /**
  126. * @var SSH2
  127. */
  128. private $ssh2;
  129. /**
  130. * @var SFTP
  131. */
  132. private $sftp;
  133. /**
  134. * @var string
  135. */
  136. private $bindConfigFile;
  137. /**
  138. * @var string
  139. */
  140. private $zoneFilePath;
  141. /**
  142. * @var string
  143. */
  144. private $zoneFile;
  145. /**
  146. * @var array
  147. */
  148. private $records;
  149. /**
  150. * @var int
  151. */
  152. private $ttl;
  153. /**
  154. * Test connection with Server
  155. * @throws DNSSubmoduleException if fails to connect
  156. */
  157. public function testConnection()
  158. {
  159. if ( !$this->ssh2 ) $this->establishSSH2Connection();
  160. if ( !$this->fileExists($this->parsePathToConfig()) )
  161. {
  162. throw new DNSSubmoduleException('File: ' . $this->parsePathToConfig() . ' doesn\'t exists', SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  163. }
  164. if ( !$this->directoryExists($this->parsePathToConfig(true) . 'zones') && !$this->createDirectory($this->parsePathToConfig(true) . 'zones') )
  165. {
  166. throw new DNSSubmoduleException('This module requires writable directory: ' . $this->parsePathToConfig(true) . 'zones');
  167. }
  168. if ( !$this->directoryExists($this->config['pathtobackup']) )
  169. {
  170. throw new DNSSubmoduleException('Directory: ' . $this->config['pathtobackup'] . ' doesn\'t exists', SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  171. }
  172. $tempFileName = $this->parsePathToConfig(true) . uniqid('dnsm2temp', false);
  173. if ( $this->upload($tempFileName, 'Lorem ipsum') )
  174. {
  175. $this->removeFile($tempFileName);
  176. }
  177. else
  178. {
  179. throw new DNSSubmoduleException('Account you specified don\'t have read/write prmissions in bind directory', SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  180. }
  181. return true;
  182. }
  183. /**
  184. * Gets records from server
  185. * @param bool $recordTypeFilter
  186. * @return array
  187. * @throws DNSSubmoduleException
  188. */
  189. public function getRecords( $recordTypeFilter = false )
  190. {
  191. $this->records = $this->records ? : $this->parseFileToStructure($recordTypeFilter);
  192. return $this->records;
  193. }
  194. /**
  195. * Adds a record to server
  196. * @param dns\record\Record $record
  197. * @return bool
  198. * @throws DNSSubmoduleException
  199. * @throws DNSSubmoduleHiddenException
  200. */
  201. public function addRecord( dns\record\Record $record )
  202. {
  203. //Since addRecord can be called in loop we need to download last good configuration every run
  204. //So when somenone will try to create wrong record the function will revert file one step back instead of to point when loop started
  205. $this->updateZoneVariables();
  206. //We just add this to structure and parse it later so no need to do it multiple times since it will give same result
  207. if ( !$this->records )
  208. {
  209. $this->records = $this->parseFileToStructure();
  210. }
  211. if(($record->type === 'SOA') && ($soaRecordPositon = array_search('SOA', array_column($this->records, 'type'), true)) !== false)
  212. {
  213. $record->line = $this->records[$soaRecordPositon]->line;
  214. return $this->editRecord($record);
  215. }
  216. $record->name = $this->replaceNameToBind9Record($record->name);
  217. if ( !$this->validateName($record->name) )
  218. {
  219. throw new DNSSubmoduleHiddenException('The Record ' . $record->name . ' name is invalid and can\'t be saved');
  220. }
  221. //Before adding we should check if the record is valid
  222. $record->rdata->validate();
  223. $this->records[] = $record;
  224. $this->backupConfig($this->bindConfigFile);
  225. $this->backupZone($this->zoneFile);
  226. $this->incrementSOASerial();
  227. $this->upload($this->zoneFilePath, $this->parseStructureToFile());
  228. $zoneValidation = $this->checkZoneChanges();
  229. if ( $zoneValidation !== true )
  230. {
  231. $this->upload($this->zoneFilePath, $this->zoneFile);
  232. throw new DNSSubmoduleHiddenException($record->__toString() .' -> '.$zoneValidation);
  233. }
  234. $this->reloadZone();
  235. return false;
  236. }
  237. /**
  238. * Edits record by matching it with line
  239. * @param dns\record\Record $record
  240. * @return bool
  241. * @throws DNSSubmoduleException
  242. * @throws DNSSubmoduleHiddenException
  243. */
  244. public function editRecord( dns\record\Record $record )
  245. {
  246. if ( !$this->zoneFile )
  247. {
  248. $this->updateZoneVariables();
  249. }
  250. $record->name = $this->replaceNameToBind9Record($record->name);
  251. if ( !$this->validateName($record->name) )
  252. {
  253. throw new DNSSubmoduleHiddenException('Invalid Record Name: ' . $record->name);
  254. }
  255. //Validate data sent by user
  256. $record->rdata->validate();
  257. if ( !$this->records )
  258. {
  259. $this->records = $this->parseFileToStructure();
  260. }
  261. /** @var dns\record\Record $structRecord */
  262. foreach ( $this->records as $index => $structRecord )
  263. {
  264. if ( (int)$structRecord->line === (int)$record->line )
  265. {
  266. $record->ttl = $record->ttl ? : 14400;
  267. $this->records[$index] = $record;
  268. }
  269. }
  270. $this->backupConfig($this->bindConfigFile);
  271. $this->backupZone($this->zoneFile);
  272. $this->incrementSOASerial();
  273. if ( !$this->upload($this->zoneFilePath, $this->parseStructureToFile()) ) throw new DNSSubmoduleException('We were unable to edit record: ' . $record->type . ' in file: ' . $this->zoneFilePath);
  274. $zoneValidation = $this->checkZoneChanges();
  275. if ( $zoneValidation !== true )
  276. {
  277. $this->upload($this->zoneFilePath, $this->zoneFile);
  278. throw new DNSSubmoduleException($zoneValidation);
  279. }
  280. $this->reloadZone();
  281. return true;
  282. }
  283. /**
  284. * Removes record by it's line
  285. * @param dns\record\Record $record
  286. * @throws DNSSubmoduleException
  287. */
  288. public function deleteRecord( dns\record\Record $record )
  289. {
  290. if ( !$this->zoneFilePath )
  291. {
  292. $this->updateZoneVariables();
  293. }
  294. if ( !$this->records )
  295. {
  296. $this->records = $this->parseFileToStructure();
  297. }
  298. /** @var dns\record\Record $structRecord */
  299. foreach ( $this->records as $index => $structRecord )
  300. {
  301. if ( (int)$structRecord->line === (int)$record->line )
  302. {
  303. unset($this->records[$index]);
  304. }
  305. }
  306. $this->backupConfig($this->bindConfigFile);
  307. $this->backupZone($this->zoneFile);
  308. $this->incrementSOASerial();
  309. $this->upload($this->zoneFilePath, $this->parseStructureToFile());
  310. $this->reloadZone();
  311. }
  312. /**
  313. * Checks if zone exists
  314. * @return bool
  315. * @throws DNSSubmoduleException
  316. */
  317. public function zoneExists()
  318. {
  319. return array_key_exists($this->getDomainWithoutDot(), $this->getZones());
  320. }
  321. public function activateZone()
  322. {
  323. $configFile = $this->downloadFile($this->parsePathToConfig());
  324. $zoneFileName = $this->parsePathToConfig(true);
  325. $zoneFileName .= 'zones/db.' . $this->getDomainWithoutDot();
  326. //Get template config file and parse
  327. $path = __DIR__ . '/bind9/sampleZoneMasterConfigFile.txt';
  328. $zoneConfigString = file_get_contents($path);
  329. if ( $this->config['notify_slaves'] === 'on' && trim($this->config['slaves']))
  330. {
  331. $notifyStatus = 'yes';
  332. $slaveIpsArray = explode(',', $this->config['slaves']);
  333. $slaveIps = implode(';', $slaveIpsArray) . ((count($slaveIpsArray)) ? ';' : '');
  334. }
  335. else
  336. {
  337. $notifyStatus = 'no';
  338. $slaveIps = '';
  339. }
  340. $zoneConfigString = str_replace(
  341. [
  342. '{$domain}',
  343. '{$file}',
  344. '{$notify}',
  345. '{$slaveips}',
  346. '{$clientCustom}'
  347. ],
  348. [
  349. $this->getDomainWithoutDot(),
  350. $zoneFileName,
  351. $notifyStatus,
  352. $slaveIps,
  353. $this->config['customSettings']
  354. ],
  355. $zoneConfigString);
  356. $configFile .= $zoneConfigString;
  357. //Get template zone file and parse
  358. $path = __DIR__ . '/bind9/sampleZoneFile.txt';
  359. $zoneFileString = file_get_contents($path);
  360. $ipReplacement = filter_var($this->ip) ? $this->ip : $this->config['default_ip'];
  361. $zoneFileString = str_replace(
  362. [
  363. '{$domain}',
  364. '{$ttl}',
  365. '{$mname}',
  366. '{$rname}',
  367. '{$refresh}',
  368. '{$retry}',
  369. '{$expire}',
  370. '{$ip}'
  371. ],
  372. [
  373. $this->getDomainReplacement(),
  374. 14400,
  375. $this->getDomainReplacement($this->server->getNameservers(1)->name),
  376. $this->config['rname'],
  377. $this->config['refresh'],
  378. $this->config['retry'],
  379. $this->config['expire'],
  380. $ipReplacement
  381. ],
  382. $zoneFileString);
  383. $this->backupConfig($configFile);
  384. $pathToConfig = $this->parsePathToConfig();
  385. if ( !$this->upload($pathToConfig, $configFile) ) throw new DNSSubmoduleException('We couldn\'t create file: ' . $pathToConfig);
  386. if ( !$this->upload($zoneFileName, $zoneFileString) ) throw new DNSSubmoduleException('We couldn\'t create file: ' . $zoneFileName);
  387. $this->reloadBind9();
  388. $this->synchronizeSlaves($this->getDomainWithoutDot());
  389. }
  390. /**
  391. * @param string $domain
  392. * @param string $type
  393. * @return bool
  394. * @throws DNSSubmoduleException
  395. */
  396. protected function synchronizeSlaves( $domain, $type = 'Activate' )
  397. {
  398. if(!trim($this->config['slaves'])) return;
  399. $slaves = explode(',', $this->config['slaves']);
  400. foreach ( $slaves as $slaveIp )
  401. {
  402. $slaveIp = trim($slaveIp);
  403. if(!filter_var($slaveIp,FILTER_VALIDATE_IP))
  404. {
  405. continue;
  406. }
  407. $this->establishSFTPConnection(trim($slaveIp));
  408. $this->establishSSH2Connection(trim($slaveIp));
  409. $configFile = $this->downloadFile($this->parsePathToConfig());
  410. $zoneFileName = 'db.' . $domain;
  411. if ( $type === 'Activate' )
  412. {
  413. $path = __DIR__ . '/bind9/sampleZoneSlaveConfigFile.txt';
  414. $zoneConfigString = file_get_contents($path);
  415. $zoneConfigString = str_replace(
  416. [
  417. '{$domain}',
  418. '{$masterIp}',
  419. '{$file}'
  420. ],
  421. [
  422. $domain,
  423. $this->config['master_ip'],
  424. $zoneFileName
  425. ], $zoneConfigString);
  426. $configFile .= $zoneConfigString;
  427. $this->upload($this->parsePathToConfig(), $configFile);
  428. }
  429. elseif ( $type === 'Terminate' )
  430. {
  431. $regexDomain = '/zone\s+"' . preg_quote($domain, '/') . '\.*"\s+.+?\n};/msi';
  432. $configFile = preg_replace($regexDomain, '', $configFile, 1);
  433. $this->upload($this->parsePathToConfig(), $configFile);
  434. }
  435. $this->reloadBind9();
  436. }
  437. $this->sftp = null;
  438. $this->ssh2 = null;
  439. return true;
  440. }
  441. /**
  442. * Removes zone
  443. * @return bool
  444. * @throws DNSSubmoduleException
  445. */
  446. public function terminateZone()
  447. {
  448. $this->updateZoneVariables();
  449. if ( !$this->zoneFilePath )
  450. {
  451. throw new DNSSubmoduleException('Missing file property in bind9 zone config file');
  452. }
  453. //Remove from main file
  454. $regexDomain = '/zone\s+"' . preg_quote($this->getDomainWithoutDot(), '/') . '\.*"\s+.+?\n};/msi';
  455. $configFile = preg_replace($regexDomain, '', $this->bindConfigFile, 1);
  456. $this->backupConfig($configFile);
  457. $this->backupZone($this->zoneFile);
  458. //Remove zone file
  459. if ( !$this->removeFile($this->zoneFilePath) )
  460. {
  461. throw new DNSSubmoduleException('Can\'t delete zone file wrong file or no permissions');
  462. }
  463. //Remove zone from config
  464. $pathToConfig = $this->parsePathToConfig();
  465. if ( !$this->upload($pathToConfig, $configFile) ) throw new DNSSubmoduleException('We couldn\'t remove zone from config file in path: ' . $pathToConfig);
  466. $this->reloadZone();
  467. return $this->synchronizeSlaves($this->getDomainWithoutDot(), 'Terminate');
  468. }
  469. /**
  470. * Gets Zones
  471. * @return array
  472. * @throws DNSSubmoduleException
  473. */
  474. public function getZones()
  475. {
  476. $zoneFile = $this->downloadFile($this->parsePathToConfig());
  477. $values = explode("\n", $zoneFile);
  478. $out = [];
  479. foreach ( $values as $nol => $value )
  480. {
  481. if ( preg_match('/in-addr\.arpa/', $value) )
  482. {
  483. continue;
  484. }
  485. if ( !preg_match('/zone\s"(.+)"/', $value, $domain) )
  486. {
  487. continue;
  488. }
  489. if ( !filter_var($domain[1], FILTER_VALIDATE_DOMAIN) )
  490. {
  491. continue;
  492. }
  493. $domain[1] = rtrim($domain[1], '.');
  494. $out[$domain[1]] = '';
  495. }
  496. return $out;
  497. }
  498. /**
  499. * Uploads string to file specified in first parameter to server
  500. *
  501. * @param string $fileLoc Location of the remote file
  502. * @param string $stringFile String with \n sepearation to be placed in file
  503. *
  504. * @return bool
  505. * @throws DNSSubmoduleException if can't connect to host
  506. */
  507. private function upload( $fileLoc, $stringFile )
  508. {
  509. if ( !$this->sftp ) $this->establishSFTPConnection();
  510. return $this->sftp->put($fileLoc, $stringFile, SFTP::SOURCE_STRING);
  511. }
  512. /**
  513. * Reloads the zone on remote
  514. * @return boolean
  515. * @throws DNSSubmoduleException
  516. */
  517. private function reloadZone()
  518. {
  519. if ( !$this->ssh2 ) $this->establishSSH2Connection();
  520. return $this->ssh2->exec('rndc reload ' . $this->getDomainWithoutDot());
  521. }
  522. /**
  523. * Reloads bind 9 (required in zone activation)
  524. * @return string
  525. * @throws DNSSubmoduleException
  526. */
  527. private function reloadBind9()
  528. {
  529. if ( !$this->ssh2 ) $this->establishSSH2Connection();
  530. return $this->ssh2->exec('systemctl reload bind9');
  531. }
  532. /**
  533. * Checks if file exists on remote
  534. * @param string $file file
  535. * @return bool
  536. * @throws DNSSubmoduleException if couldn't connect
  537. */
  538. private function fileExists( $file )
  539. {
  540. if ( !$this->sftp ) $this->establishSFTPConnection();
  541. return ($this->sftp->get($file) !== false);
  542. }
  543. /**
  544. * Removes file from remote
  545. * @param string $fileLocation Location of the file to be removed
  546. *
  547. * @return bool
  548. * @throws DNSSubmoduleException when he can't find file or have no permissions
  549. */
  550. private function removeFile( $fileLocation )
  551. {
  552. if ( !$this->sftp ) $this->establishSFTPConnection();
  553. return $this->sftp->delete($fileLocation);
  554. }
  555. /**
  556. * Gets file from server
  557. * @param string $file downlaods file from remote
  558. * @return mixed
  559. * @throws DNSSubmoduleException
  560. */
  561. private function downloadFile( $file )
  562. {
  563. if ( !$this->sftp ) $this->establishSFTPConnection();
  564. return $this->sftp->get($file);
  565. }
  566. /**
  567. * Checks if file exists on remote
  568. * @param string $dir absolute dir
  569. * @return bool
  570. * @throws DNSSubmoduleException
  571. */
  572. private function directoryExists( $dir )
  573. {
  574. if ( !$this->sftp ) $this->establishSFTPConnection();
  575. return $this->sftp->chdir($dir);
  576. }
  577. /**
  578. * Creates Directory
  579. * @param string $dir
  580. * @return bool depending on result
  581. * @throws DNSSubmoduleException if couldn't connect to remote
  582. */
  583. private function createDirectory( $dir )
  584. {
  585. if ( !$this->sftp ) $this->establishSFTPConnection();
  586. return $this->sftp->mkdir($dir);
  587. }
  588. /**
  589. * Backups the zone file to backup/zones/ directory
  590. * @param $data
  591. * @return bool
  592. * @throws DNSSubmoduleException
  593. */
  594. private function backupZone( $data )
  595. {
  596. if ( !$this->sftp ) $this->establishSFTPConnection();
  597. $backupDir = rtrim($this->config['pathtobackup'], '/') . '/zones/';
  598. if ( !$this->directoryExists($backupDir) )
  599. {
  600. $this->createDirectory($backupDir);
  601. }
  602. $domain = str_replace('.', '', $this->getDomainWithoutDot());
  603. $backupDir .= $domain . '/';
  604. if ( !$this->directoryExists($backupDir) )
  605. {
  606. $this->createDirectory($backupDir);
  607. }
  608. $filename = date('YmdHis') . '.txt';
  609. return $this->upload($backupDir . $filename, $data);
  610. }
  611. /**
  612. * Backups the config file to backup/config
  613. * @param $data
  614. * @return bool
  615. * @throws DNSSubmoduleException
  616. */
  617. private function backupConfig( $data )
  618. {
  619. if ( !$this->sftp ) $this->establishSFTPConnection();
  620. $backupDir = rtrim($this->config['pathtobackup'], '/') . '/config/';
  621. if ( !$this->directoryExists($backupDir) && !$this->createDirectory($backupDir) )
  622. {
  623. throw new DNSSubmoduleException('We couldn\'t create diretory for backup config');
  624. }
  625. $filename = date('YmdHis') . '.txt';
  626. return $this->upload($backupDir . $filename, $data);
  627. }
  628. /**
  629. * Parses the config path given by user to valid one
  630. * @param bool $dir Specifies should function return the dir name or file name
  631. * @return string
  632. */
  633. public function parsePathToConfig( $dir = false )
  634. {
  635. $path = $this->config['pathtoconfig'];
  636. if ( $dir ) return rtrim(str_replace('named.conf.local', '', $path), '/') . '/';
  637. //If there is no slash at the end and it's not config file we add slash
  638. if ( substr($path, -1, 1) !== '/' && strpos($path, 'named.conf.local') === false )
  639. {
  640. $path .= '/';
  641. }
  642. //If there is no name of config at the end we add the default
  643. if ( strpos($path, 'named.conf.local') === false )
  644. {
  645. $path .= 'named.conf.local';
  646. }
  647. return $path;
  648. }
  649. /**
  650. * Adds to array of soa properties property which will be later used for creating record
  651. *
  652. * @param mixed $soaProp Property to be addded
  653. * @throws DNSSubmoduleException if there is to many elements in array
  654. */
  655. private function addSOAInformation( $soaProp )
  656. {
  657. $this->soa[] = $soaProp;
  658. if ( count($this->soa) > 12 )
  659. {
  660. throw new DNSSubmoduleException('There is problably missing ")" in your SOA record definition');
  661. }
  662. }
  663. /**
  664. * Creates SOA record from properties previously addded to $this->soa
  665. *
  666. * @param int|string $defTtl Default ttl of zone if not specified soa ttl will be set to 14400
  667. *
  668. * @return dns\record\Record
  669. * @throws DNSSubmoduleException
  670. */
  671. public function createSOAFromArray( $defTtl = 14400 )
  672. {
  673. $recordTypeIndex = $this->getIndexOfRecordType($this->soa, 'SOA');
  674. $record = new dns\record\Record();
  675. $record->name = $this->soa[0];
  676. $record->type = 'SOA';
  677. $ttlAndClass = $this->getTTLAndClassFromValues($recordTypeIndex, $this->soa, $defTtl);
  678. $record->ttl = $ttlAndClass['ttl'];
  679. $record->class = $ttlAndClass['class'];
  680. //Make sure we have name|class|ttl to ommit later checking for it
  681. if ( $recordTypeIndex === 2 )
  682. {
  683. array_splice($this->soa, 2, 1, [$record->class, $record->ttl]);
  684. }
  685. $soaRecord = new dns\record\type\SOA();
  686. $soaRecord->mname = $this->soa[4];
  687. $soaRecord->rname = $this->soa[5];
  688. $soaRecord->serial = TimeUnitsAliassesHelper::convertToSeconds($this->soa[6]);
  689. $soaRecord->refresh = TimeUnitsAliassesHelper::convertToSeconds($this->soa[7]);
  690. $soaRecord->retry = TimeUnitsAliassesHelper::convertToSeconds($this->soa[8]);
  691. $soaRecord->expire = TimeUnitsAliassesHelper::convertToSeconds($this->soa[9]);
  692. $soaRecord->minimum = TimeUnitsAliassesHelper::convertToSeconds($this->soa[10]);
  693. $record->rdata = $soaRecord;
  694. $record->customData = $record->rdlength = '';
  695. $record->line = $this->soa['line'];
  696. return $record;
  697. }
  698. /**
  699. * Gets location of zone file from main config of bind
  700. * @param string $bindConfigFile bindConfigFile in string downloaded from bind server
  701. * @param string $zoneName Name of zone to search
  702. *
  703. * @return string
  704. * @throws DNSSubmoduleException if can't find zone or when there is no path specified
  705. */
  706. private function getZoneFileLocationFrombindConfigFile( $bindConfigFile, $zoneName )
  707. {
  708. $preg = '/zone\s+"' . preg_quote($zoneName, '/') . '\.*".+?\n};/msi';
  709. if ( !preg_match($preg, $bindConfigFile, $matches) )
  710. {
  711. throw new DNSSubmoduleException('We couldn\'t find ' . $zoneName . ' in your bindconfig file');
  712. }
  713. if ( !preg_match('/file\s"(.+?)"/mi', $matches[0], $zonePath) )
  714. {
  715. throw new DNSSubmoduleException('We couldn\'t find path in your ' . $zoneName . ' config section in your bind config file');
  716. }
  717. return $zonePath[1];
  718. }
  719. /**
  720. * Convert Time aliasses to normal int seconds
  721. * @param $line
  722. *
  723. * @return string|string[]|null
  724. * @throws Exception
  725. */
  726. private function convertTime( $line )
  727. {
  728. $values = preg_split('/\s+/', $line);
  729. foreach ( $values as &$value )
  730. {
  731. if ( preg_match('/(\d+)([MHDW])/i', $value, $match) )
  732. {
  733. $replacement = TimeUnitsAliassesHelper::convertToSeconds($match[0]);
  734. $value = preg_replace('/(\d+)([MHDW])/i', $replacement, $value);
  735. }
  736. }
  737. return implode(' ', $values);
  738. }
  739. /**
  740. * Replaces @ and whitespaces to domain names with dot attached
  741. * @param string $line
  742. * @return string
  743. */
  744. private function replaceName( $line )
  745. {
  746. //Explode line to array
  747. $values = preg_split('/\s+/', $line);
  748. //Record name is blank/whitespace/@ so we remove place there zone name
  749. if ( $values[0] === '@' || !trim($values[0]) )
  750. {
  751. $values[0] = $this->getDomainReplacement();
  752. $line = implode(' ', $values);
  753. }
  754. return $line;
  755. }
  756. /**
  757. * Creates record name with @ if record name matches domain and trims domain from the end
  758. * @param string $name
  759. * @return string
  760. */
  761. private function replaceNameToBind9Record( $name )
  762. {
  763. $name = trim(preg_replace('/\.?' . preg_quote(IdnaHelper::idnaDecode($this->getDomainWithoutDot())) . '\.+$/', '', $name, 1));
  764. return $name === '' ? '@' : $name;
  765. }
  766. /**
  767. * Filters the records by their type
  768. * @param array $lines
  769. * @param string $recordTypeFilter
  770. * @return array
  771. */
  772. private function filterRecords( $lines, $recordTypeFilter )
  773. {
  774. foreach ( $lines as $nol => $line )
  775. {
  776. //Explode line to array
  777. $values = preg_split('/\s+/', $line);
  778. //Get record type from array
  779. $recordType = $this->getRecordType($values);
  780. //If filter is set and record doesn't match we remove it
  781. if ( $recordType !== $recordTypeFilter )
  782. {
  783. unset($lines[$nol]);
  784. continue;
  785. }
  786. }
  787. return $lines;
  788. }
  789. /**
  790. * Removes everything in line after ";"
  791. * @param string $line
  792. * @return false|string
  793. */
  794. private function removeComments( $line )
  795. {
  796. return (($index = strpos($line, ';')) !== false) ? substr($line, 0, $index) : $line;
  797. }
  798. /**
  799. * Returns the index of type property in record
  800. * @param $values
  801. * @param $recordType
  802. * @return false|int|string
  803. */
  804. private function getIndexOfRecordType( $values, $recordType )
  805. {
  806. return array_search($recordType, $values, false);
  807. }
  808. /** Returns record type
  809. * @param array $values exploded line by \s
  810. * @return bool
  811. */
  812. private function getRecordType( $values )
  813. {
  814. foreach ( $values as $value )
  815. {
  816. if ( in_array($value, $this->availableTypes, false) )
  817. {
  818. return $value;
  819. }
  820. }
  821. return false;
  822. }
  823. /**
  824. * Function looks for $TTL in file
  825. * @param array $values exploded zone file by "\n"
  826. * @return int|null
  827. */
  828. private function findDefaultTtl( array $values )
  829. {
  830. $ttl = null;
  831. foreach ( $values as $line )
  832. {
  833. if ( !preg_match('/\$TTL\s+(\d+)/', $line, $match) )
  834. {
  835. continue;
  836. }
  837. $ttl = (int)$match[1];
  838. break;
  839. }
  840. return $ttl;
  841. }
  842. /** Gets SOA record from zone file
  843. * @param array $lines
  844. * @param int $ttl
  845. * @return dns\record\Record|null
  846. * @throws DNSSubmoduleException
  847. */
  848. private function getSoaRecord( array $lines, $ttl = 14400 )
  849. {
  850. $out = null;
  851. foreach ( $lines as $nol => $line )
  852. {
  853. $values = preg_split('/\s+/', $line);
  854. $recordType = $this->getRecordType($values);
  855. if ( $recordType === 'SOA' )
  856. {
  857. //Check if SOA is multiline
  858. $multilineSoa = strpos($line, '(') !== false && strpos($line, ')') === false;
  859. $this->soa['line'] = (int)$nol;
  860. break;
  861. }
  862. }
  863. if ( !isset($this->soa['line']) ) return $out;
  864. if ( isset($multilineSoa) )
  865. {
  866. $lines[$this->soa['line']] = $this->replaceName($lines[$this->soa['line']]);
  867. if ( $multilineSoa )
  868. {
  869. //We don't know length of SOA so we go from it's beggining to the end of the file
  870. foreach ( range($this->soa['line'], count($lines) - 1) as $i )
  871. {
  872. //Spliting by whitespace
  873. $values = preg_split('/\s+/', $lines[$i]);
  874. //We add all values from line if for some reason someone specified more than one
  875. foreach ( $values as $soaProp )
  876. {
  877. if ( ($soaProp === '(') || !trim($soaProp) )
  878. {
  879. continue;
  880. }
  881. if ( $soaProp !== ')' )
  882. {
  883. $this->addSOAInformation($soaProp);
  884. }
  885. else
  886. {
  887. $out = $this->createSOAFromArray($ttl);
  888. }
  889. }
  890. //We found end of the soa we can leave
  891. if ( strpos($lines[$i], ')') !== false )
  892. {
  893. break;
  894. }
  895. }
  896. }
  897. else
  898. {
  899. //Spliting by whitespace
  900. $values = preg_split('/\s+/', $lines[$this->soa['line']]);
  901. //We add all values from line
  902. foreach ( $values as $soaProp )
  903. {
  904. if ( $soaProp !== '(' && $soaProp !== ')' && $soaProp && $soaProp !== ' ' )
  905. {
  906. $this->addSOAInformation($soaProp);
  907. }
  908. }
  909. $out = $this->createSOAFromArray($ttl);
  910. }
  911. }
  912. return $out;
  913. }
  914. /** Removes comments, empty lines,$ORIGIN and $TTL lines and converts time aliases to seconds
  915. * @param array $lines
  916. * @return array
  917. * @throws Exception when can't convert time
  918. */
  919. private function setAndRemoveUsefullValues( $lines )
  920. {
  921. foreach ( $lines as $nol => $line )
  922. {
  923. if ( !trim($line) )
  924. {
  925. unset($lines[$nol]);
  926. continue;
  927. }
  928. $lines[$nol] = $this->removeComments($line);
  929. if ( strpos($line, '$ORIGIN') !== false )
  930. {
  931. unset($lines[$nol]);
  932. continue;
  933. }
  934. if ( preg_match('/\$TTL\s+(\d+)/', $line, $match) )
  935. {
  936. $this->ttl = (int)$match[1];
  937. unset($lines[$nol]);
  938. continue;
  939. }
  940. //All time aliasses to seconds
  941. $lines[$nol] = $this->convertTime($lines[$nol]);
  942. }
  943. return $lines;
  944. }
  945. /**
  946. * Generate the bind9 file from structure
  947. * @return string
  948. */
  949. private function parseStructureToFile()
  950. {
  951. $recordsArray = [
  952. '$ORIGIN ' . $this->getDomainReplacement(),
  953. '$TTL ' . $this->ttl
  954. ];
  955. usort($this->records, static function ( $a, $b )
  956. {
  957. $recordAllignment = ['SOA', 'A', 'AAAA', 'NS', 'MX', 'TXT'];
  958. if ( !in_array($a->type, $recordAllignment, true) && !in_array($b->type, $recordAllignment, true) ) return 0;
  959. if ( !in_array($a->type, $recordAllignment, true) ) return 1;
  960. if ( !in_array($b->type, $recordAllignment, true) ) return -1;
  961. if ( $a->type === $b->type )
  962. {
  963. return $a->name < $b->name ? -1 : 1;
  964. }
  965. return array_search($a->type, $recordAllignment, true) < array_search($b->type, $recordAllignment, true) ? -1 : 1;
  966. });
  967. /** @var dns\record\Record $record */
  968. foreach ( $this->records as $record )
  969. {
  970. $record->name = $this->replaceNameToBind9Record($record->name);
  971. $recordArray = [
  972. $record->name,
  973. $record->class,
  974. $record->ttl,
  975. $record->type
  976. ];
  977. $recordsArray[] = implode(' ', $recordArray) . ' ' . $record->rdata->toString();
  978. }
  979. return implode("\n", $recordsArray) . "\n";
  980. }
  981. /**
  982. * Removes soa from file checking for it beeing multiline
  983. *
  984. * @param array $lines file sp
  985. * @param int $offset the location of soa in lines
  986. * @return array
  987. */
  988. private function removeSoaFromLine( array $lines, $offset = 0 )
  989. {
  990. $multilineSoa = (strpos($lines[$offset], '(') !== false) && (strpos($lines[$offset], ')') === false);
  991. if ( $multilineSoa )
  992. {
  993. //Removes lines until finds closing round bracket
  994. foreach ( range($offset, count($lines) - 1) as $i )
  995. {
  996. $templn = $lines[$i];
  997. unset($lines[$i]);
  998. if ( strpos($templn, ')') !== false )
  999. {
  1000. break;
  1001. }
  1002. }
  1003. }
  1004. else
  1005. {
  1006. unset($lines[$offset]);
  1007. }
  1008. return $lines;
  1009. }
  1010. /**
  1011. * Function checks if there is $ORIGIN in file and matches zone name
  1012. * Not used cause of the $ORIGIN beeing not required in file
  1013. * @param $lines
  1014. * @return mixed|null
  1015. * @throws DNSSubmoduleException
  1016. */
  1017. private function validateFile( $lines )
  1018. {
  1019. $name = null;
  1020. foreach ( $lines as $line )
  1021. {
  1022. if ( !preg_match('/\$ORIGIN\s+(.+)/', $line, $match) )
  1023. {
  1024. continue;
  1025. }
  1026. $name = $match[1];
  1027. break;
  1028. }
  1029. if ( !$name )
  1030. {
  1031. throw new DNSSubmoduleException('Missing $ORIGIN in your file');
  1032. }
  1033. if ( $name !== $this->getDomainWithoutDot() && $name !== $this->getDomainReplacement() )
  1034. {
  1035. throw new DNSSubmoduleException('$ORIGIN in file doesn\'t match the zone name');
  1036. }
  1037. return $name;
  1038. }
  1039. /**
  1040. * Since it's ain't easy task here is function for it
  1041. * @param int $indexOfRecordType
  1042. * @param array $values
  1043. * @param int $defaultTTL
  1044. * @param string $defaultClass
  1045. * @return array
  1046. * @throws DNSSubmoduleException
  1047. */
  1048. private function getTTLAndClassFromValues( $indexOfRecordType, array $values, $defaultTTL, $defaultClass = 'IN' )
  1049. {
  1050. $out = [];
  1051. //We have to check where is ttl and class cause they can be shuffled or one of them can be ommited
  1052. if ( $indexOfRecordType === 3 )
  1053. {
  1054. if ( is_numeric(trim($values[2])) )
  1055. {
  1056. $out['class'] = $values[1];
  1057. $out['ttl'] = (int)$values[2];
  1058. }
  1059. else
  1060. {
  1061. $out['class'] = $values[2];
  1062. $out['ttl'] = (int)$values[1];
  1063. }
  1064. }
  1065. elseif ( $indexOfRecordType === 2 )
  1066. {
  1067. //If there is only 2 we will have to check which one
  1068. if ( is_numeric(trim($values[1])) )
  1069. {
  1070. $out['ttl'] = (int)$values[1];
  1071. $out['class'] = $defaultClass;
  1072. }
  1073. else
  1074. {
  1075. $out['class'] = $values[1];
  1076. $out['ttl'] = (int)$defaultTTL;
  1077. }
  1078. }
  1079. else
  1080. {
  1081. throw new DNSSubmoduleException('Error while processing your record: ' . $values[0] . ' your record values are in wrong order');
  1082. }
  1083. return $out;
  1084. }
  1085. /**
  1086. * Validates the domain name if there is no dot it wasn't the propper domain name
  1087. * @param $name
  1088. * @return bool
  1089. */
  1090. private function validateName( $name )
  1091. {
  1092. return substr($name, -1, 1) !== '.';
  1093. }
  1094. /**
  1095. * @param array $lines lines of file
  1096. * @param int $line number of line to edit
  1097. * @param int $counter specifies offset from which start removing files
  1098. * @return array with unset lines
  1099. * @throws DNSSubmoduleException if we couldn't find the ")" in file
  1100. */
  1101. private function clearMultiline( $lines, $line, $counter = 0 )
  1102. {
  1103. //Make sure to don't look for lines that are not in file
  1104. $linesToEndOfFile = count($lines) - $line;
  1105. while ( $counter < $linesToEndOfFile )
  1106. {
  1107. if ( $counter > 10 )
  1108. {
  1109. throw new DNSSubmoduleException('Missing closing round bracket in zone file in SOA record you tried to edit');
  1110. }
  1111. //We have to save it somewhere before deleting it
  1112. $currentLine = $lines[$line + $counter];
  1113. unset($lines[$line + $counter]);
  1114. if ( strpos($currentLine, ')') !== false )
  1115. {
  1116. break;
  1117. }
  1118. $counter++;
  1119. }
  1120. return $lines;
  1121. }
  1122. /**
  1123. * For matching and replacing purposes returns domain name with dot attached
  1124. * @param null|string $domain If provided function will append dot to parameter domain instead of global zone
  1125. * @return string
  1126. */
  1127. private function getDomainReplacement( $domain = null )
  1128. {
  1129. $targetDomain = $domain ? : $this->domain;
  1130. return rtrim($targetDomain, '.') . '.';
  1131. }
  1132. /**
  1133. * For matching purposes removes dot (if exist) from the end of the domain
  1134. * @param null|string $domain If provided function will trim dot from parameter domain instead of global zone
  1135. * @return string
  1136. */
  1137. private function getDomainWithoutDot( $domain = null )
  1138. {
  1139. $targetDomain = $domain ? : $this->domain;
  1140. return rtrim($targetDomain, '.');
  1141. }
  1142. /**
  1143. * Sets path to zone and config and downloads file
  1144. * @return bool
  1145. * @throws DNSSubmoduleException
  1146. */
  1147. private function updateZoneVariables()
  1148. {
  1149. $this->bindConfigFile = $this->downloadFile($this->parsePathToConfig());
  1150. $this->zoneFilePath = $this->getZoneFileLocationFrombindConfigFile($this->bindConfigFile, $this->getDomainWithoutDot());
  1151. $this->zoneFile = $this->downloadFile($this->zoneFilePath);
  1152. return true;
  1153. }
  1154. /**Creates SSH2 instance
  1155. * @param null $ip
  1156. * @return SSH2
  1157. * @throws DNSSubmoduleException
  1158. */
  1159. private function establishSSH2Connection( $ip = null )
  1160. {
  1161. $connctIp = $ip ? : $this->config['hostname'];
  1162. $this->ssh2 = new SSH2($connctIp);
  1163. if ( $this->config['rsa'] )
  1164. {
  1165. $auth = new RSA();
  1166. $auth->loadKey($this->config['rsa']);
  1167. }
  1168. else
  1169. {
  1170. $auth = $this->config['password'];
  1171. }
  1172. if ( !$this->ssh2->login($this->config['username'], $auth) )
  1173. {
  1174. throw new DNSSubmoduleException('Wrong authentication details');
  1175. }
  1176. return $this->ssh2;
  1177. }
  1178. /** Creates SFTP Instance
  1179. * @param null $ip
  1180. * @return SFTP
  1181. * @throws DNSSubmoduleException
  1182. */
  1183. private function establishSFTPConnection( $ip = null )
  1184. {
  1185. $connctIp = $ip ? : $this->config['hostname'];
  1186. $this->sftp = new SFTP($connctIp);
  1187. if ( !$this->sftp->login($this->config['username'], $this->config['password']) )
  1188. {
  1189. throw new DNSSubmoduleException('We couldn\'t upload changes to your server check permissions for given account');
  1190. }
  1191. return $this->sftp;
  1192. }
  1193. /**
  1194. * @param $recordTypeFilter
  1195. * @return array
  1196. * @throws DNSSubmoduleException
  1197. */
  1198. private function parseFileToStructure( $recordTypeFilter = false )
  1199. {
  1200. $this->updateZoneVariables();
  1201. $lines = explode("\n", $this->zoneFile);
  1202. $out = [];
  1203. // $this->validateFile($lines);
  1204. $lines = $this->setAndRemoveUsefullValues($lines);
  1205. $this->ttl = $this->ttl ? : 14400;
  1206. $soa = $this->getSoaRecord($lines, $this->ttl);
  1207. //If SOA in file we add it to out records and remove it so it makes it easier to parse file later (since soa can be multiline)
  1208. if ( $soa )
  1209. {
  1210. $out[] = $soa;
  1211. $lines = $this->removeSoaFromLine($lines, $soa->line);
  1212. }
  1213. //If multiple SOA records we throw exception
  1214. if ( $this->getSoaRecord($lines) )
  1215. {
  1216. throw new DNSSubmoduleException('You can have only one SOA record in file');
  1217. }
  1218. //If filter records types is set we filter any other out
  1219. if ( $recordTypeFilter )
  1220. {
  1221. $lines = $this->filterRecords($lines, $recordTypeFilter);
  1222. }
  1223. //Everything else in file should be record
  1224. foreach ( $lines as $nol => $line )
  1225. {
  1226. if ( !trim($line) ) continue;
  1227. //Replace @/blank/whitespace in first val for valid domain names
  1228. $line = $this->replaceName($line);
  1229. $values = preg_split('/\s+/', $line);
  1230. $recordType = $this->getRecordType($values);
  1231. $indexOfRecordType = $this->getIndexOfRecordType($values, $recordType);
  1232. //If record type is not supported
  1233. if ( !$recordType )
  1234. {
  1235. throw new DNSSubmoduleException('We don\'t support this record type in line: ' . ($nol + 1));
  1236. }
  1237. $record = new dns\record\Record();
  1238. $record->name = $values[0];
  1239. $record->line = $nol;
  1240. $record->type = $values[$indexOfRecordType];
  1241. $record->customData = $record->rdlength = '';
  1242. $ttlandclass = $this->getTTLAndClassFromValues($indexOfRecordType, $values, $this->ttl);
  1243. $record->class = $ttlandclass['class'];
  1244. $record->ttl = $ttlandclass['ttl'];
  1245. //We split string by whitespace but txt record type has spaces in one of the params so we have to merge them
  1246. if ( $recordType === 'TXT' )
  1247. {
  1248. preg_match('/".+"/', $line, $match);
  1249. $values[$indexOfRecordType + 1] = str_replace("\t", ' ', $match[0]);
  1250. }
  1251. $className = 'MGModule\DNSManager2\mgLibs\custom\dns\record\type\\' . $recordType;
  1252. $additionalVars = array_keys(get_class_vars($className));
  1253. $recordTypeClass = new $className;
  1254. foreach ( $additionalVars as $index => $prop )
  1255. {
  1256. $recordTypeClass->$prop = $values[$indexOfRecordType + 1 + $index];
  1257. }
  1258. $record->rdata = $recordTypeClass;
  1259. $out[] = $record;
  1260. }
  1261. return $out;
  1262. }
  1263. /**
  1264. * Since Bin9 requires to update this value every time
  1265. */
  1266. private function incrementSOASerial()
  1267. {
  1268. foreach ( $this->records as &$structRecord )
  1269. {
  1270. if ( $structRecord->type === 'SOA' )
  1271. {
  1272. $structRecord->rdata->serial++;
  1273. }
  1274. }
  1275. return $this;
  1276. }
  1277. /**
  1278. * Runs named-checkzone on domain
  1279. * @return string
  1280. * @throws DNSSubmoduleException
  1281. */
  1282. private function checkZoneChanges()
  1283. {
  1284. $this->establishSSH2Connection();
  1285. $pathToZone = $this->zoneFilePath;
  1286. $result = $this->ssh2->exec("named-checkzone {$this->getDomainWithoutDot()} {$pathToZone}");
  1287. if ( $this->ssh2->getExitStatus() )
  1288. {
  1289. return $result;
  1290. }
  1291. return true;
  1292. }
  1293. }