SimpleDNS.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <?php
  2. namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
  3. use \MGModule\DNSManager2\mgLibs\custom\dns;
  4. use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
  5. use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
  6. use \MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;
  7. class SimpleDNS extends dns\SubmoduleAbstract implements
  8. interfaces\SubmoduleRDNSInterface,
  9. interfaces\SubmoduleTTLInterface,
  10. interfaces\SubmoduleImportInterface
  11. {
  12. public $configFields = array(
  13. 'username' => array(
  14. 'friendlyName' => 'Username',
  15. 'validators' => array(
  16. 'required' => 'required',
  17. )
  18. ),
  19. 'password' => array(
  20. 'friendlyName' => 'Password',
  21. 'type' => 'password',
  22. 'validators' => array(
  23. 'required' => 'required',
  24. )
  25. ),
  26. 'hostname' => array(
  27. 'friendlyName' => 'Hostname',
  28. 'validators' => array(
  29. 'required' => 'required',
  30. )
  31. ),
  32. 'ssl' => array(
  33. 'friendlyName' => 'SSL',
  34. 'type' => 'yesno',
  35. 'validators' => array(
  36. 'required' => 'required',
  37. )
  38. ),
  39. 'port' => array(
  40. 'friendlyName' => 'Port',
  41. 'type' => 'number',
  42. 'value' => '8053',
  43. 'validators' => array(
  44. 'required' => 'required',
  45. )
  46. ),
  47. 'httpauth' => array(
  48. 'friendlyName' => 'HTTP Auth Type',
  49. 'type' => 'select',
  50. 'options' => array(CURLAUTH_BASIC => 'BASIC', CURLAUTH_DIGEST => 'DIGEST'),
  51. ),
  52. 'ns1' => array(
  53. 'friendlyName' => 'Primary SOA DNS Server',
  54. 'validators' => array(
  55. 'required' => 'required',
  56. )
  57. ),
  58. // 'ns2' => array(
  59. // 'friendlyName' => 'Secondary SOA DNS Server',
  60. // ),
  61. 'email' => array(
  62. 'friendlyName' => 'Email',
  63. 'type' => 'email',
  64. 'value' => 'change@me.com',
  65. 'validators' => array(
  66. 'required' => 'required',
  67. )
  68. ),
  69. 'refresh' => array(
  70. 'friendlyName' => 'Refresh',
  71. 'type' => 'number',
  72. 'value' => '10800',
  73. 'validators' => array(
  74. 'min' => 1,
  75. 'required' => 'required',
  76. )
  77. ),
  78. 'retry' => array(
  79. 'friendlyName' => 'Retry',
  80. 'type' => 'number',
  81. 'value' => '3600',
  82. 'validators' => array(
  83. 'min' => 1,
  84. 'required' => 'required',
  85. )
  86. ),
  87. 'expire' => array(
  88. 'friendlyName' => 'Expire',
  89. 'type' => 'number',
  90. 'value' => '777600',
  91. 'validators' => array(
  92. 'min' => 1,
  93. 'required' => 'required',
  94. )
  95. ),
  96. 'ttl' => array(
  97. 'friendlyName' => 'TTL',
  98. 'type' => 'number',
  99. 'value' => '360',
  100. 'validators' => array(
  101. 'min' => 1,
  102. 'required' => 'required',
  103. )
  104. ),
  105. );
  106. private $primaryDNS = '';
  107. private $defaultTTL = '';
  108. public $availableTypes = array('A', 'AAAA', 'NS', 'MX', 'CNAME', 'TXT', 'SRV', 'PTR');
  109. /**
  110. * Connect to server using user query
  111. * @param type $function
  112. * @param type $params
  113. * @return boolean
  114. */
  115. private function get($function, $params = array())
  116. {
  117. $login = urlencode($this->config['username']);
  118. $password = $this->config['password'];
  119. $url = ($this->config['ssl'] == '1' ?
  120. 'https://'.$this->config['hostname'].':'.$this->config['port'] :
  121. 'http://'.$this->config['hostname'].':'.$this->config['port']).'/'.$function;
  122. $post = '';
  123. if(is_array($params))
  124. {
  125. foreach($params as $key => $value)
  126. {
  127. $value = urlencode($value);
  128. $post .= "{$key}={$value}&";
  129. }
  130. }
  131. $ch = curl_init();
  132. $chOptions = array (
  133. CURLOPT_URL => $url,
  134. CURLOPT_RETURNTRANSFER => true,
  135. CURLOPT_SSL_VERIFYPEER => false,
  136. CURLOPT_SSL_VERIFYHOST => false,
  137. CURLOPT_TIMEOUT => 30,
  138. CURLOPT_HEADER => 0,
  139. CURLOPT_POST => true,
  140. CURLOPT_POSTFIELDS => $post,
  141. CURLOPT_USERPWD => $login.':'.$password,
  142. CURLOPT_HTTPAUTH => $this->config['httpauth']
  143. );
  144. curl_setopt_array($ch, $chOptions);
  145. $out = curl_exec($ch);
  146. curl_close($ch);
  147. if(curl_errno($ch) != 0 || $out === false) {
  148. throw new exceptions\DNSSubmoduleException('cURL error: ' . (curl_error($ch)?: 'Unknown Error'), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  149. }
  150. if(preg_match('/^\*(.)+\*$/', strtolower(trim($out))))
  151. {
  152. throw new exceptions\DNSSubmoduleException(($out?:'Unknown Error'), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  153. }
  154. return $out;
  155. }
  156. private function addDot(dns\record\Record &$record)
  157. {
  158. if($record->type == 'CNAME' && $record->rdata->toString() == '@')
  159. {
  160. return;
  161. }
  162. if($record->type == 'SRV' || $record->type == 'MX' || $record->type == 'NS' || $record->type == 'CNAME' || $record->type == 'PTR')
  163. {
  164. $str = $record->rdata->toString();
  165. $str = rtrim($str, '.') . '.';
  166. $record->rdata->fromString($str);
  167. }
  168. }
  169. private function deleteDot(dns\record\Record &$record)
  170. {
  171. if($record->type == 'SRV' || $record->type == 'MX' || $record->type == 'NS' || $record->type == 'CNAME' || $record->type == 'PTR')
  172. {
  173. $record->value = rtrim($record->value, '.');
  174. }
  175. }
  176. public function testConnection()
  177. {
  178. $login = urlencode($this->config['username']);
  179. $password = html_entity_decode($this->config['password']);
  180. $url = ($this->config['ssl'] == '1' ?
  181. 'https://'.$this->config['hostname'].':'.$this->config['port'].'/getzone?zone=mgdomain.com' :
  182. 'http://'.$this->config['hostname'].':'.$this->config['port']).'/getzone?zone=mgdomain.com';
  183. $ch = curl_init();
  184. $chOptions = array (
  185. CURLOPT_URL => $url,
  186. CURLOPT_RETURNTRANSFER => true,
  187. CURLOPT_SSL_VERIFYPEER => false,
  188. CURLOPT_SSL_VERIFYHOST => false,
  189. CURLOPT_TIMEOUT => 15,
  190. CURLOPT_HEADER => 0,
  191. CURLOPT_USERPWD => $login.':'.$password,
  192. CURLOPT_HTTPAUTH => $this->config['httpauth']
  193. );
  194. curl_setopt_array($ch, $chOptions);
  195. $out = curl_exec($ch);
  196. $info = curl_getinfo($ch);
  197. switch ($info['http_code'])
  198. {
  199. case 401:
  200. throw new exceptions\DNSSubmoduleException('Error: Invalid Connection Data', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  201. }
  202. if(strpos(strtolower($out), 'not found') !== false || strpos(strtoupper($out), 'IN SOA') !== false || strpos(strtolower($out), 'path does not exist') !== false ) {
  203. return true;
  204. }
  205. if(curl_errno($ch) != 0 || $out === false) {
  206. throw new exceptions\DNSSubmoduleException('cURL error: ' . (curl_error($ch)?: 'Unknown Error'), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  207. } else {
  208. throw new exceptions\DNSSubmoduleException('Error: '.$out, dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  209. }
  210. }
  211. public function zoneExists()
  212. {
  213. try {
  214. $out = $this->get('zonelist', array());
  215. $zones = explode("\n", trim($out));
  216. foreach($zones as $zone)
  217. {
  218. if(trim(strtolower($zone)) == strtolower($this->domain))
  219. {
  220. return true;
  221. }
  222. }
  223. return false;
  224. } catch( exceptions\DNSSubmoduleException $e) {
  225. if($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR) {
  226. return false;
  227. }
  228. throw $e;
  229. }
  230. }
  231. public function getRecords($recordType = false)
  232. {
  233. $out = $this->get('getzone', array(
  234. 'zone' => $this->domain
  235. ));
  236. $types = $this->availableTypes;
  237. $exploded = explode("\n", $out);
  238. foreach($exploded as $x){
  239. if(strpos($x, 'TXT') === false)
  240. $zone[] = preg_replace('/(;.*)$/m', '', $x);
  241. else
  242. $zone[] = $x;
  243. }
  244. $records = array();
  245. $line_number = -1;
  246. foreach($exploded as $line)
  247. {
  248. $line_number++;
  249. //find SOA
  250. if($this->primaryDNS == '')
  251. {
  252. if(strpos($line, 'SOA'))
  253. {
  254. $ex = explode("\t", trim($line));
  255. $this->primaryDNS = trim(str_replace(array("(","\t",""),"", $ex[3]), ".");
  256. }
  257. //default TTL?
  258. if($this->defaultTTL == '' && strpos($line, '$TTL') !== false)
  259. {
  260. $ttl = (string)$line;
  261. $ex = explode(" ", trim($ttl));
  262. $this->defaultTTL = trim($ex[1]);
  263. }
  264. }
  265. $pad = array_pad(preg_split("/[\s,]+/", $line), 4, ''); //two line record parser ;)
  266. $ex = array_merge(array_slice($pad,0, 4), array(implode(' ', array_slice($pad,4))));
  267. if($ex[0] == '$TTL')
  268. {
  269. $defaultTTL = $ex[1];
  270. }
  271. if(!is_numeric($ex[1]))
  272. {
  273. $name = $ex[0];
  274. $ttl = $defaultTTL;
  275. $type = $ex[1];
  276. $value = $type === 'MX' ? $ex[2].' '.$ex[3] : implode(' ', array_splice($ex, 2));
  277. }
  278. else
  279. {
  280. $name = $ex[0];
  281. $ttl = $ex[1];
  282. $type = $ex[2];
  283. $value = $type === 'MX' ? $ex[3].' '.$ex[4] : implode(' ', array_splice($ex, 3));
  284. }
  285. if(empty($type))
  286. {
  287. continue;
  288. }
  289. $type = strtoupper($type);
  290. if(!in_array($type, $recordType !== false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes()) ) {
  291. continue;
  292. }
  293. if($type === 'TXT' && !$name)
  294. {
  295. $reverseRecordsArray = array_reverse($records);
  296. foreach ($reverseRecordsArray as $record)
  297. {
  298. if($record->name)
  299. {
  300. $name = $record->name;
  301. break;
  302. }
  303. }
  304. }
  305. $record = new dns\record\Record();
  306. $record->name = $name;
  307. $record->ttl = empty($ttl)?$this->defaultTTL:$ttl;
  308. $record->type = $type;
  309. $record->line = $line_number;
  310. $record->createRDATAObject();
  311. $record->rdata->fromString(trim($value));
  312. $this->deleteDot($record);
  313. $records[] = $record;
  314. }
  315. return $records;
  316. }
  317. private function recordToParamsArray(dns\record\Record $record) {
  318. $this->addDot($record);
  319. $relativeName = $record->nameToRelative($this->domain);
  320. return array(
  321. 'zone' => $this->domain,
  322. 'name' => empty($relativeName) ? '@' : $relativeName,
  323. 'type' => $record->type,
  324. 'ttl' => $record->ttl,
  325. 'data' => $record->rdata->toString(),
  326. );
  327. }
  328. public function addRecord(dns\record\Record $record)
  329. {
  330. $ret = $this->get('addrecord', $this->recordToParamsArray($record));
  331. if(strpos($ret, "OK") === false) {
  332. throw new exceptions\DNSSubmoduleException($ret, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  333. }
  334. }
  335. public function editRecord(dns\record\Record $record)
  336. {
  337. $recordsList = $this->getRecords();
  338. foreach ($recordsList as $rec)
  339. {
  340. if($record->line == $rec->line)
  341. {
  342. $ret = $this->get('removerecord', $this->recordToParamsArray($rec));
  343. if(strpos($ret, "OK") === false)
  344. {
  345. throw new exceptions\DNSSubmoduleException($ret, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  346. }
  347. $ret = $this->get('addrecord', $this->recordToParamsArray($record));
  348. if(strpos($ret, "OK") === false)
  349. {
  350. throw new exceptions\DNSSubmoduleException($ret, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  351. }
  352. break;
  353. }
  354. }
  355. }
  356. /**
  357. *
  358. * @param type $data
  359. * @return boolean
  360. */
  361. public function deleteRecord(dns\record\Record $record)
  362. {
  363. $ret = $this->get('removerecord', $this->recordToParamsArray($record));
  364. if(strpos($ret, "OK") === false) {
  365. throw new exceptions\DNSSubmoduleException($ret, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  366. }
  367. }
  368. public function activateZone()
  369. {
  370. $data = "\$TTL ".$this->config['ttl'].
  371. "\n@ IN SOA\t(\t".$this->config['ns1'].".\n".
  372. str_replace('@', '.', $this->config['email']).".\n".
  373. time()."\n".
  374. $this->config['refresh']."\n".
  375. $this->config['retry']."\n".
  376. $this->config['expire']."\n".
  377. $this->config['ttl'].")\n".
  378. "\t".$this->config['ttl']."\tNS\t".$this->config['ns1'].".\n";
  379. // if($this->config['ns2'] != '')
  380. //{
  381. // $data.="\n\t".$this->config['ttl']."\tNS\t".$this->config['ns2'].".\n";
  382. //}
  383. $this->get('updatezone', array(
  384. 'zone' => $this->domain,
  385. 'data' => $data
  386. ));
  387. if(!$this->zoneExists())
  388. {
  389. throw new exceptions\DNSSubmoduleException('Cannot create zone', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  390. }
  391. }
  392. public function terminateZone()
  393. {
  394. $this->get('removezone', array(
  395. 'zone' => $this->domain
  396. ));
  397. if($this->zoneExists())
  398. {
  399. throw new exceptions\DNSSubmoduleException('Cannot terminate zone', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  400. }
  401. }
  402. public function getZones()
  403. {
  404. $ret = $this->get('zonelist', array());
  405. $zones = explode("\n", trim($ret));
  406. $out = array();
  407. foreach($zones as $zone)
  408. {
  409. $out[trim($zone)] = '';
  410. }
  411. return $out;
  412. }
  413. }