CPanel.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. use \MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel as cPanelUapi;
  8. use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
  9. class CPanel extends dns\SubmoduleAbstract implements
  10. interfaces\SubmoduleIPInterface,
  11. interfaces\SubmoduleRDNSInterface,
  12. interfaces\SubmoduleTTLInterface,
  13. interfaces\SubmoduleDNSSecInterface,
  14. interfaces\SubmoduleImportInterface
  15. {
  16. public $configFields = array(
  17. 'username' =>array (
  18. 'friendlyName' => 'Username',
  19. 'validators' => array(
  20. 'required' => 'required',
  21. )
  22. ),
  23. 'password' =>array (
  24. 'friendlyName' => 'User Password',
  25. 'type'=> 'password',
  26. ),
  27. 'resellerAccount' =>array (
  28. 'friendlyName' => 'Reseller Account',
  29. 'type'=> 'yesno',
  30. ),
  31. 'hash' =>array (
  32. 'friendlyName' => 'Access Key',
  33. 'type'=> 'textarea',
  34. ),
  35. 'hostname' =>array (
  36. 'friendlyName' => 'Hostname/IP',
  37. 'validators' => array(
  38. 'required' => 'required',
  39. )
  40. ),
  41. 'ssl' =>array (
  42. 'friendlyName' => 'Enable SSL',
  43. 'type'=> 'yesno',
  44. ),
  45. 'timeout' =>array (
  46. 'friendlyName' => 'Timeout',
  47. 'help' => 'Set timeout in second for the API connection. If value is empty, the default value is 30',
  48. ),
  49. 'default_ip' =>array (
  50. 'friendlyName' => 'Default IP',
  51. 'validators' => array(
  52. 'required' => 'required',
  53. 'pattern' => Patterns::IP4_OR_IP6,
  54. )
  55. ),
  56. 'allow_create' =>array (
  57. 'friendlyName' => 'In cPanel Zones',
  58. 'type' => 'yesno',
  59. 'help' => 'Allow to create zones when already in CPanel as account',
  60. ),
  61. 'template' =>array (
  62. 'friendlyName' => 'Template',
  63. 'help' => 'Default zone template from your cPanel'
  64. ),
  65. );
  66. public $availableTypes = array('A', 'AAAA','CAA', 'NS', 'MX', 'CNAME', 'DNAME', 'LOC', 'TXT', 'SRV', 'PTR', 'AFSDB', 'NAPTR', 'RP', 'SOA');
  67. protected $uapi;
  68. public function testConnection()
  69. {
  70. try
  71. {
  72. $this->get('get_nameserver_config');
  73. return true;
  74. }
  75. catch( exceptions\DNSSubmoduleException $e )
  76. {
  77. $this->get('listzones', ['api.chunk.enable' => 1, 'api.chunk.size' => 1]);
  78. return true;
  79. }
  80. }
  81. public function getRecords($recordType = false) {
  82. $out = $this->get('dumpzone', array(
  83. 'domain' => $this->domain
  84. ));
  85. $return = array();
  86. if(!isset($out->zone[0]->record)){
  87. throw new exceptions\DNSSubmoduleException('Unable to fetch records from server', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  88. }
  89. foreach($out->zone[0]->record as $r) {
  90. $recordAdapter = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$r->type.'Adapter';
  91. if (class_exists($recordAdapter))
  92. {
  93. $return[] = $recordAdapter::toDnsManagerRecord((array)$r);
  94. }
  95. elseif ( in_array((string)$r->type, $recordType !== false ? [strtoupper($recordType)] : $this->getAvailableRecordTypes()) )
  96. {
  97. $return[] = dns\record\Record::tryToCreateFromArray((array)$r);
  98. }
  99. }
  100. return $return;
  101. }
  102. public function addRecord(dns\record\Record $record)
  103. {
  104. $adapterClassName = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$record->type.'Adapter';
  105. if(class_exists($adapterClassName))
  106. {
  107. /** @var dns\record\Record $record */
  108. $record = new $adapterClassName($record);
  109. }
  110. $input = $record->toMergedArray(false);
  111. $input['domain'] = $this->domain;
  112. $input['name'] = IdnaHelper::idnaEncode($record->nameToAbsolute($this->domain));
  113. $this->logAction('addRecord', $input, $record);
  114. $this->get('addzonerecord', $input);
  115. return true;
  116. }
  117. public function editRecord(dns\record\Record $record) {
  118. $records = $this->getRecords();
  119. if(empty($records))
  120. return false;
  121. $lines = array();
  122. foreach($records as $r) {
  123. $lines[$r['line']] = $r;
  124. }
  125. if(!isset($lines[$record['line']]) || !$record->isEqualTo($lines[$record['line']])) {
  126. $adapterClassName = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$record->type.'Adapter';
  127. if(class_exists($adapterClassName))
  128. {
  129. /** @var dns\record\Record $record */
  130. $record = new $adapterClassName($record);
  131. }
  132. $input = $record->toMergedArray(false);
  133. $input['domain'] = $this->domain;
  134. $input['name'] = IdnaHelper::idnaEncode($record->nameToAbsolute($this->domain));
  135. $this->get('editzonerecord', $input);
  136. }
  137. }
  138. public function deleteRecord(dns\record\Record $record) {
  139. $this->get('removezonerecord', array(
  140. 'zone' => $this->domain,
  141. 'line' => $record['line']
  142. ));
  143. }
  144. public function zoneExists()
  145. {
  146. try
  147. {
  148. $this->get('dumpzone', ['domain' => $this->domain]);
  149. }
  150. catch( exceptions\DNSSubmoduleException $e )
  151. {
  152. if( $e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR )
  153. {
  154. return false;
  155. }
  156. throw $e;
  157. }
  158. return true;
  159. }
  160. public function activateZone() {
  161. if($this->ip != '') {
  162. if(!filter_var($this->ip, FILTER_VALIDATE_IP)) {
  163. throw new exceptions\DNSSubmoduleException('IP is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  164. }
  165. } else {
  166. $this->ip = $this->config['default_ip'];
  167. }
  168. $params = array(
  169. 'domain' => $this->domain,
  170. 'ip' => $this->ip
  171. );
  172. if($this->config['resellerAccount'] == "on"){
  173. $params['trueowner'] = $this->config['username'];
  174. }
  175. if($this->config['template'] != "")
  176. $params['template'] = $this->config['template'];
  177. try {
  178. $this->get('adddns', $params);
  179. } catch (exceptions\DNSSubmoduleException $e) {
  180. if(preg_match('/is owned by another user/', $e->getMessage()) && $this->config['allow_create'] == 'on') {
  181. $res = $this->get('listaccts', array('want' => 'user', 'searchtype' => 'domain', 'search' => $this->domain, 'searchmethod' => 'exact'));
  182. $user = $res->acct[0]->user;
  183. $params['trueowner'] = $user;
  184. $this->get('adddns', $params);
  185. return ;
  186. }
  187. throw $e;
  188. }
  189. }
  190. public function terminateZone() {
  191. $this->get('killdns', array(
  192. 'domain' => $this->domain
  193. ));
  194. }
  195. public function getZones()
  196. {
  197. $out = array();
  198. $res = $this->get('listzones', array());
  199. foreach($res->zone as $zone)
  200. {
  201. $out[(string)$zone->domain] = '';
  202. }
  203. //TODO: tu skonczyłem - dokończyc whm1
  204. try
  205. {
  206. $res = $this->get('listaccts', array('want' => 'domain,ip'));
  207. foreach ($res->acct as $zone)
  208. {
  209. if(isset($out[(string)$zone->domain]))
  210. {
  211. $out[(string)$zone->domain] = (string)$zone->ip;
  212. }
  213. }
  214. }
  215. catch(exceptions\DNSSubmoduleException $exc)
  216. {
  217. }
  218. return $out;
  219. }
  220. private function get($function, $params=array()) {
  221. $params['api.version'] = 1;
  222. $url = ($this->config['ssl']? 'https://'.$this->config['hostname'].':2087' : 'http://'.$this->config['hostname'].':2086').'/json-api/'.$function;
  223. $ch = curl_init();
  224. if(is_array($params)) {
  225. $url .= '?';
  226. foreach($params as $key=>$value) {
  227. $value = urlencode($value);
  228. $url .= "{$key}={$value}&";
  229. }
  230. }
  231. $url = trim($url, '&');
  232. $this->log('REQUEST: ' . $url);
  233. $chOptions = array (
  234. CURLOPT_URL => $url,
  235. CURLOPT_RETURNTRANSFER => true,
  236. CURLOPT_SSL_VERIFYPEER => false,
  237. CURLOPT_SSL_VERIFYHOST => false,
  238. CURLOPT_TIMEOUT => is_numeric($this->config['timeout']) ? intval($this->config['timeout']) : 30
  239. );
  240. if($this->config['hash'] != '') {
  241. $header[0] = 'Authorization: WHM '.$this->config['username'].':'.str_replace(array("\r", "\n"),"", $this->config['hash']);
  242. $chOptions[CURLOPT_HTTPHEADER] = $header;
  243. } elseif($this->config['password'] != '') {
  244. $chOptions[CURLOPT_USERPWD] = $this->config['username'].':'.$this->config['password'];
  245. } else {
  246. throw new exceptions\DNSSubmoduleException('Password or Access key is required', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  247. }
  248. curl_setopt_array($ch, $chOptions);
  249. $out = curl_exec($ch);
  250. $this->log('RESPONSE: ' . $out);
  251. if (curl_errno($ch)) {
  252. throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  253. }
  254. $out_info = curl_getinfo($ch);
  255. if($out_info['http_code'] != 200) {
  256. if($out_info['http_code'] == 301 || $out_info['http_code'] == 302){
  257. throw new exceptions\DNSSubmoduleException('Module require SSL', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  258. }
  259. }
  260. curl_close($ch);
  261. if(strpos($out, 'SSL encryption is required for access to this server') !== FALSE) {
  262. throw new exceptions\DNSSubmoduleException('SSL encryption is required for access to this server', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  263. }
  264. $a = json_decode($out);
  265. if($a===FALSE) {
  266. throw new exceptions\DNSSubmoduleException('Unable to parse response', dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
  267. }
  268. if ($a->cpanelresult->error && $a->cpanelresult->data->result == 0)
  269. {
  270. throw new exceptions\DNSSubmoduleException($a->cpanelresult->error, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  271. }
  272. if(isset($a->status) && $a->status == 0){
  273. throw new exceptions\DNSSubmoduleException($a->statusmsg?:'Unknown Error', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  274. }
  275. if(isset($a->data->result) && $a->data->result == 0) {
  276. throw new exceptions\DNSSubmoduleException($a->data->reason?:'Unknown Error', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  277. }
  278. if(!empty($a->error)) {
  279. throw new exceptions\DNSSubmoduleException($a->error, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  280. }
  281. if(isset($a->metadata->result) && $a->metadata->result == 0) {
  282. throw new exceptions\DNSSubmoduleException($a->metadata->reason, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  283. }
  284. // if(!isset($a->data)) {
  285. // throw new exceptions\DNSSubmoduleException("There is no data in output", dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
  286. // }
  287. return isset($a->data)?$a->data:$a;
  288. }
  289. private function logAction($action, $request, $response)
  290. {
  291. $addonConfig = \MGModule\DNSManager2\addon::config();
  292. logmodulecall(
  293. $addonConfig['name'],
  294. 'cPanel '.($action),
  295. array($request),
  296. array($response),
  297. null,
  298. array($this->config['hostname'], $this->config['hostname'], $this->config['hash'])
  299. );
  300. }
  301. public function getSignKeys(){
  302. $fetchDSRecords = $this->get('fetch_ds_records_for_domains', array('domain' => $this->domain));
  303. $dnssec = new \MGModule\DNSManager2\mgLibs\custom\dns\dnssec\DnsSec();
  304. foreach($fetchDSRecords->domains[0]->ds_records->keys as $dsKey => $dsRecord){
  305. foreach($dsRecord->digests as $diggestKey => $diggestRecord){
  306. $ds = new dns\record\type\DS();
  307. $ds->setKeytag($dsRecord->key_tag);
  308. $ds->setAlgorithm($dsRecord->algo_num);
  309. $ds->setDigestType($diggestRecord->algo_num);
  310. $ds->setDigest($diggestRecord->digest);
  311. $dnssec->addDs($ds);
  312. }
  313. switch (strtolower($dsRecord->key_type)){
  314. case 'csk':
  315. $dnskey = new dns\record\type\DNSKEY();
  316. $dnskey->setFlags($dsRecord->flags);
  317. $dnskey->setProtocol(3);
  318. $dnskey->setAlgorithm($dsRecord->algo_num);
  319. $zoneKey = new dns\dnssec\CSK();
  320. $zoneKey->setId($dsKey);
  321. $zoneKey->setBits($dsRecord->bits);
  322. $zoneKey->setDnsKey($dnskey);
  323. $dnssec->addKey($zoneKey);
  324. break;
  325. case 'ksk':
  326. $dnskey = new dns\record\type\DNSKEY();
  327. $dnskey->setFlags($dsRecord->flags);
  328. $dnskey->setProtocol(3);
  329. $dnskey->setAlgorithm($dsRecord->algo_num);
  330. $ksk = new dns\dnssec\KSK();
  331. $ksk->setId($dsKey);
  332. $ksk->setBits($dsRecord->bits);
  333. $ksk->setDnsKey($dnskey);
  334. $dnssec->addKey($ksk);
  335. break;
  336. case 'zsk':
  337. $dnskey = new dns\record\type\DNSKEY();
  338. $dnskey->setFlags($dsRecord->flags);
  339. $dnskey->setProtocol(3);
  340. $dnskey->setAlgorithm($dsRecord->algo_num);
  341. $zsk = new dns\dnssec\ZSK();
  342. $zsk->setId($dsKey);
  343. $zsk->setBits($dsRecord->bits);
  344. $zsk->setDnsKey($dnskey);
  345. $dnssec->addKey($zsk);
  346. break;
  347. }
  348. }
  349. return $dnssec;
  350. }
  351. public function sign(){
  352. $this->get('enable_dnssec_for_domains', array('domain' => $this->domain));
  353. }
  354. public function unsign(){
  355. $this->get('disable_dnssec_for_domains', array('domain' => $this->domain));
  356. }
  357. public function rectify(){
  358. return true;
  359. }
  360. public function isSigned(){
  361. $fetchDSRecords = $this->get('fetch_ds_records_for_domains', array('domain' => $this->domain));
  362. $checkDsRecords = $fetchDSRecords->domains[0]->ds_records;
  363. if(empty(get_object_vars($checkDsRecords)))
  364. return false;
  365. return true;
  366. }
  367. /* No need to use it anymore. It is not deleted to have it in future implementations - just in case */
  368. /* private function uAPI()
  369. {
  370. $res = $this->get('listaccts', array('want' => 'user', 'searchtype' => 'domain', 'search' => $this->domain, 'searchmethod' => 'exact'));
  371. $user = $res->acct[0]->user;
  372. if (!is_null($this->uapi))
  373. {
  374. return $this->uapi;
  375. }
  376. $this->uapi = new cPanelUapi\Uapi();
  377. $this->uapi->setLogin($this->config, $user)
  378. ->createSession();
  379. return $this->uapi;
  380. }
  381. */
  382. }