DirectAdmin.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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 DirectAdmin extends dns\SubmoduleAbstract implements interfaces\SubmoduleIPInterface, interfaces\SubmoduleRDNSInterface, interfaces\SubmoduleImportInterface, interfaces\SubmoduleDNSSecInterface {
  8. public $configFields = array(
  9. 'username' => array (
  10. 'friendlyName' => 'Username',
  11. 'validators' => array(
  12. 'required' => 'required',
  13. )
  14. ),
  15. 'password' => array (
  16. 'friendlyName' => 'Password',
  17. 'type' => 'password',
  18. 'validators' => array(
  19. 'required' => 'required',
  20. )
  21. ),
  22. 'hostname' => array (
  23. 'friendlyName' => 'Hostname',
  24. 'validators' => array(
  25. 'required' => 'required',
  26. )
  27. ),
  28. 'port' => array (
  29. 'friendlyName' => 'Port',
  30. 'value' => 2222,
  31. 'validators' => array(
  32. 'required' => 'required',
  33. )
  34. ),
  35. 'ssl' => array (
  36. 'friendlyName' => 'SSL',
  37. 'type' => 'yesno'
  38. ),
  39. 'default_ip' =>array (
  40. 'friendlyName' => 'Default IP',
  41. 'validators' => array(
  42. 'required' => 'required',
  43. 'pattern' => Patterns::IP4_OR_IP6,
  44. )
  45. ),
  46. 'ns1' => array (
  47. 'friendlyName' => 'Nameserver 1',
  48. ),
  49. 'ns2' => array (
  50. 'friendlyName' => 'Nameserver 2',
  51. ),
  52. 'full_mx_records' => array (
  53. 'friendlyName' => 'Full MX Records',
  54. 'type'=> 'yesno',
  55. ),
  56. );
  57. public $availableTypes = array('A', 'NS', 'MX', 'CNAME', 'TXT', 'AAAA', 'SRV', 'DS', 'PTR');
  58. private function get($function, $params = array()) {
  59. $port = $this->config['port']?:'2222';
  60. $username = urlencode($this->config['username']);
  61. $password = $this->config['password'];
  62. $url = ($this->config['ssl'] === 'on' ? 'https://'.$username.':'.urlencode($password).'@'.$this->config['hostname'].':'.$port : 'http://'.$username.':'.urlencode($password).'@'.$this->config['hostname'].':'.$port).'/'.$function;
  63. if( is_array($params) )
  64. {
  65. $url .= '?' . http_build_query($params);
  66. }
  67. $this->log('REQUEST: ' . $url);
  68. $ch = curl_init();
  69. $chOptions = array (
  70. CURLOPT_URL => trim($url, '&').'&urlencoded=yes',
  71. CURLOPT_RETURNTRANSFER => true,
  72. CURLOPT_SSL_VERIFYPEER => false,
  73. CURLOPT_SSL_VERIFYHOST => false,
  74. CURLOPT_TIMEOUT => 30
  75. );
  76. curl_setopt_array($ch, $chOptions);
  77. $out = curl_exec($ch);
  78. $this->log('RESPONSE: ' . $out);
  79. if (curl_errno($ch)) {
  80. throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  81. }
  82. curl_close($ch);
  83. $this->checkOutput($out);
  84. //parsujemy
  85. $out1 = urldecode($out);
  86. parse_str($out1, $parsed);
  87. if(isset($parsed['error']) && $parsed['error'] > 0) {
  88. throw new exceptions\DNSSubmoduleException($parsed['text'].' '.$parsed['details'], dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  89. }
  90. if($parsed === FALSE) {
  91. throw new exceptions\DNSSubmoduleException("Unknown error", dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  92. }
  93. return $out;
  94. }
  95. public function testConnection() {
  96. $out = $this->get('CMD_API_SHOW_USER_USAGE', array('user' => $this->config['username']));
  97. if(strpos($out, '<html>') !== false)
  98. throw new exceptions\DNSSubmoduleException('Incorrect username or password', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  99. }
  100. public function zoneExists() {
  101. try {
  102. $out = $this->get('CMD_API_DNS_ADMIN', array(
  103. 'domain' => $this->domain
  104. ));
  105. if(strpos($out, '<html>') !== false) {
  106. return false;
  107. }
  108. } catch (exceptions\DNSSubmoduleException $e) {
  109. if($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR) {
  110. return false;
  111. }
  112. throw $e;
  113. }
  114. return true;
  115. }
  116. public function getRecords($recordType = false) {
  117. $out = $this->get('CMD_API_DNS_ADMIN', array(
  118. 'domain' => $this->domain,
  119. 'urlencoded' => 'yes',
  120. 'full_mx_records' => $this->config['full_mx_records'] == 'on' ? 'yes' : 'no'
  121. ));
  122. /** TRZEBA TO ZMIENIC!!! **/
  123. $lines = explode("\n", $out);
  124. $arr = array();
  125. foreach($lines as $ex) {
  126. $line = explode('=', $ex, 2); //type=name=value&name1=value1
  127. $data = explode('&', $line[1]);
  128. foreach($data as $d) {
  129. $x = explode('=', $d, 2); //name=value
  130. if(isset($x[0]) && isset($x[1])) {
  131. $arr[$line[0]][][urldecode($x[0])] = urldecode($x[1]);
  132. }
  133. }
  134. }
  135. $out = array();
  136. $i = 0;
  137. foreach($arr as $type => $r) {
  138. foreach($r as $records) {
  139. if(in_array($type, $recordType!==false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes())) {
  140. foreach($records as $name => $value) {
  141. if(empty($name)) {
  142. continue;
  143. }
  144. $record = new dns\record\Record();
  145. $record->line = $i;
  146. $record->name = $name;
  147. $record->type = $type;
  148. $record->createRDATAObject();
  149. $record->rdata->fromString($value);
  150. switch($type) {
  151. case 'MX':
  152. if($this->config['full_mx_records'] != 'on'){
  153. $record->name = $this->domain . '.';
  154. $record->rdata->exchange = $name;
  155. }
  156. break;
  157. case 'NS':
  158. $record->name = $value;
  159. $record->rdata->setFirstProperty($name);
  160. break;
  161. }
  162. $out[] = $record;
  163. }
  164. }
  165. $i++;
  166. }
  167. }
  168. return $out;
  169. }
  170. public function addRecord(dns\record\Record $record) {
  171. $params = array(
  172. 'action' => 'add',
  173. 'domain' => $this->domain,
  174. 'type' => $record->type,
  175. );
  176. switch($record->type) {
  177. case 'NS':
  178. $params['name'] = $record->rdata->toString();
  179. $params['value'] = $record->nameToAbsolute($this->domain);
  180. break;
  181. case 'MX':
  182. if($this->config['full_mx_records'] == 'on'){
  183. $params['full_mx_records'] = 'yes';
  184. $params['mx_value'] = $record->rdata->exchange;
  185. $params['name'] = $record->nameToAbsolute($this->domain);
  186. } else {
  187. $params['full_mx_records'] = 'no';
  188. $params['name'] = $record->rdata->exchange;
  189. }
  190. $params['value'] = $record->rdata->preference;
  191. break;
  192. case 'PTR':
  193. $params['name'] = $record->nameToAbsolute($this->domain);
  194. $params['value'] = trim($record->rdata->toString(), '.').'.';
  195. break;
  196. default:
  197. $params['name'] = $record->nameToAbsolute($this->domain);
  198. $params['value'] = $record->rdata->toString();
  199. break;
  200. }
  201. $params['value'] = str_replace("\t", ' ', $params['value']);
  202. $this->get('CMD_API_DNS_ADMIN', $params);
  203. }
  204. /* DA nie posiada update */
  205. public function editRecord(dns\record\Record $record) {
  206. $records = $this->getRecords();
  207. foreach($records as $r) {
  208. if($r->line == $record->line) {
  209. $this->deleteRecord($r);
  210. break;
  211. }
  212. }
  213. $this->addRecord($record);
  214. }
  215. public function deleteRecord(dns\record\Record $record)
  216. {
  217. $params = [
  218. 'domain' => $this->domain,
  219. 'action' => 'select',
  220. 'delete' => 'yes'
  221. ];
  222. if($record->type === 'MX' && $this->config['full_mx_records'] == 'on')
  223. {
  224. $params[strtolower($record->type) . 'recs0'] = 'full_mx_records=yes&name='.$record->nameToAbsolute($this->domain).'&value='.str_replace("\t", ' ', $record->rdata->toString());
  225. $params[strtolower($record->type) . 'recs1'] = 'full_mx_records=yes&name='.urlencode($record->nameToRelative($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
  226. }
  227. else if($record->type === 'TXT')
  228. {
  229. $params[strtolower($record->type) . 'recs0'] = 'name='.urlencode($record->nameToAbsolute($this->domain)).'&value='.(str_replace("\t", ' ', $record->rdata->toString()));
  230. $params[strtolower($record->type) . 'recs1'] = 'name='.urlencode($record->nameToRelative($this->domain)).'&value='.(str_replace("\t", ' ', $record->rdata->toString()));
  231. }
  232. else
  233. {
  234. $params[strtolower($record->type) . 'recs0'] = 'name='.urlencode($record->nameToAbsolute($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
  235. $params[strtolower($record->type) . 'recs1'] = 'name='.urlencode($record->nameToRelative($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
  236. }
  237. $this->get('CMD_API_DNS_ADMIN', $params);
  238. }
  239. public function activateZone()
  240. {
  241. if($this->ip != '') {
  242. if(!filter_var($this->ip, FILTER_VALIDATE_IP)) {
  243. throw new exceptions\DNSSubmoduleException('IP is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  244. }
  245. } else {
  246. $this->ip = $this->config['default_ip'];
  247. }
  248. global $CONFIG;
  249. $ns1 = !empty($this->config['ns1'])?$this->config['ns1'] : $CONFIG['DefaultNameserver1'];
  250. $ns2 = !empty($this->config['ns2'])?$this->config['ns2'] : $CONFIG['DefaultNameserver2'];
  251. $this->get('CMD_API_DNS_ADMIN', array(
  252. 'action' => 'create',
  253. 'domain' => $this->domain,
  254. 'ip' => $this->ip,
  255. 'ns1' => $ns1,
  256. 'ns2' => $ns2
  257. ));
  258. }
  259. public function terminateZone() {
  260. $this->get('CMD_API_DNS_ADMIN', array(
  261. 'action' => 'delete',
  262. 'select0' => $this->domain
  263. ));
  264. }
  265. private function checkOutput($out){
  266. if(strpos($out, 'You cannot execute that command') !== false) {
  267. throw new exceptions\DNSSubmoduleException('You cannot execute that command', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  268. } elseif(strpos($out, 'Please enter your Username and Password') !== false) {
  269. throw new exceptions\DNSSubmoduleException('Incorrect username or password', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  270. } elseif(strpos($out, 'Your IP is blacklisted') !== false) {
  271. throw new exceptions\DNSSubmoduleException('Your IP is blacklisted', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  272. }
  273. }
  274. public function getZones()
  275. {
  276. $page = 1;
  277. $out = [];
  278. do
  279. {
  280. $return = $this->get('CMD_API_DNS_ADMIN', ['page' => $page, 'ipp' => 1000, 'json' => 'yes']);
  281. $domainsData = array_diff_key(json_decode($return, true), array_flip(['info']));
  282. foreach($domainsData as $data)
  283. {
  284. $out[$data['domain']] = '';
  285. }
  286. $page++;
  287. } while( $domainsData );
  288. return $out;
  289. }
  290. public function getSignKeys()
  291. {
  292. $ret = $this->getDnsSecKeys();
  293. parse_str($ret, $arr);
  294. $arr = array_map(function($value){
  295. return preg_split('/\s+/', $value);
  296. }, $arr);
  297. if(!isset($arr['DS']))
  298. {
  299. return null;
  300. }
  301. //KSK
  302. $dnskey = new dns\record\type\DNSKEY();
  303. $dnskey->setProtocol($arr['ksk_DNSKEY'][1]);
  304. $dnskey->setFlags($arr['ksk_DNSKEY'][0]);
  305. $dnskey->setAlgorithm($arr['ksk_DNSKEY'][2]);
  306. $dnskey->setPublicKey(implode(' ', array_slice($arr['ksk_DNSKEY'], 3)));
  307. $ksk = new dns\dnssec\KSK();
  308. $ksk->setId($arr['ksk_id'][0]);
  309. $ksk->setDnsKey($dnskey);
  310. $ksk->setExpires($arr['expiry'][0]);
  311. //ZSK
  312. $dnskey = new dns\record\type\DNSKEY();
  313. $dnskey->setProtocol($arr['zsk_DNSKEY'][1]);
  314. $dnskey->setFlags($arr['zsk_DNSKEY'][0]);
  315. $dnskey->setAlgorithm($arr['zsk_DNSKEY'][2]);
  316. $dnskey->setPublicKey(implode(' ', array_slice($arr['zsk_DNSKEY'], 3)));
  317. $zsk = new dns\dnssec\ZSK();
  318. $zsk->setId($arr['zsk_id'][0]);
  319. $zsk->setDnsKey($dnskey);
  320. $zsk->setExpires($arr['expiry'][0]);
  321. //DS
  322. $ds1 = new dns\record\type\DS($arr['DS'][3], $arr['DS'][4], $arr['DS'][5], $arr['DS'][6]);
  323. $ds2 = new dns\record\type\DS($arr['DS'][10], $arr['DS'][11], $arr['DS'][12], $arr['DS'][13]);
  324. //DNSSEC
  325. $dnssec = new dns\dnssec\DnsSec();
  326. $dnssec->addKsk($ksk);
  327. $dnssec->addZsk($zsk);
  328. $dnssec->addDs($ds1);
  329. $dnssec->addDs($ds2);
  330. return $dnssec;
  331. }
  332. public function sign()
  333. {
  334. $this->get('CMD_API_DNS_ADMIN', array(
  335. 'action' => 'dnssec',
  336. 'generate_keys' => true,
  337. 'domain' => $this->domain
  338. ));
  339. $this->get('CMD_API_DNS_ADMIN', array(
  340. 'action' => 'dnssec',
  341. 'sign_zone' => true,
  342. 'domain' => $this->domain
  343. ));
  344. }
  345. public function rectify(){
  346. $this->sign();
  347. }
  348. public function unsign()
  349. {
  350. throw new exceptions\DNSSubmoduleException('DirectAdmin does not support unsign command.', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  351. }
  352. public function isSigned()
  353. {
  354. return $this->getDnsSecKeys() ? true : false;
  355. }
  356. protected function getDnsSecKeys()
  357. {
  358. return $this->get('CMD_API_DNS_ADMIN', array(
  359. 'action' => 'dnssec',
  360. 'value' => 'get_keys',
  361. 'domain' => $this->domain
  362. ));
  363. }
  364. }