array( 'friendlyName' => 'Username', 'validators' => array( 'required' => 'required', ) ), 'apikey' => array( 'friendlyName' => 'API Access Key', 'type' => 'password', 'validators' => array( 'required' => 'required', ) ), 'account_id' => array( 'friendlyName' => 'Account ID', 'validators' => array( 'required' => 'required', ) ), 'endpoint' => array( 'friendlyName' => 'Account Is', 'type' => 'select', 'options' => array('US' => 'US-Based', 'UK' => 'UK-Based') ), 'default_ip' => array( 'friendlyName' => 'Default IP', 'validators' => array( 'pattern' => Patterns::IP4_OR_IP6, ) ), ); private $account_id; private $auth_token; private $api_end_point; private $auth_end_point; public $error; private $timeout = 10; public $results = array(); const API_VERSION = '1.0'; const US_AUTHURL = 'https://auth.api.rackspacecloud.com'; const UK_AUTHURL = 'https://lon.auth.api.rackspacecloud.com'; const UK_DNS_ENDPOINT = 'https://lon.dns.api.rackspacecloud.com'; const US_DNS_ENDPOINT = 'https://dns.api.rackspacecloud.com'; /** available records types **/ public $availableTypes = array('A', 'AAAA', 'NS', 'MX', 'CNAME', 'TXT', 'SRV'); public function login() { $auth_options = array ( 'X-Auth-User: '.$this->config['username'], 'X-Auth-Key: '.$this->config['apikey'] ); $ch = curl_init (); $url = $this->auth_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_MAXREDIRS, 4); curl_setopt($ch, CURLOPT_HTTPHEADER, $auth_options); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_URL, $url); $response = curl_exec ($ch); curl_close ($ch); preg_match ("/^HTTP\/1\.[01] (\d{3}) (.*)/", $response, $matches); if(isset($matches[1])){ if($matches[1] == "204") { preg_match ( "/X-Server-Management-Url: (.*)/", $response, $matches ); //$account = explode('/', trim($matches[1])); //$this->account_id = array_pop($account); $this->account_id = $this->config['account_id']; preg_match ("/X-Auth-Token: (.*)/", $response, $matches); $this->auth_token = trim($matches[1]); return true; } } throw new exceptions\DNSSubmoduleException($this->getErrorCodeDescription($matches[1]), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM); } public function testConnection(){ return $this->login(); } private function get($ch, $url_add) { $url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url_add; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_USERAGENT, 'Rackspace DNS PHP Binding'); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $json_response = curl_exec($ch); if (curl_errno($ch)) { throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM); } curl_close($ch); $response = json_decode($json_response, true); if(!empty($response['overLimit'])){ throw new exceptions\DNSSubmoduleException('Rackspace requests limit reached. Check your account limits.', dns\SubmoduleExceptionCodes::REQUEST_LIMIT); } return $response; } private function sendGETRequest($get_params) { if(!$this->login()) return false; $parts = explode('?', $get_params); $url = $parts[0] . ".json"; if(isset($parts[1])) $url .= '?' . $parts[1]; //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url; $ch = curl_init(); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($ch, CURLOPT_HTTPGET, true ); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Accept: application/json', 'X-Auth-Token: '.$this->auth_token )); return $this->get($ch, $url); } private function sendPOSTRequest($post_params, $json_params) { if(!$this->login()) return false; //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $post_params; $ch = curl_init(); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json_params)); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Accept: application/json', 'X-Auth-Token: '.$this->auth_token )); return $this->get($ch, $post_params); } private function sendPUTRequest($put_params, $json_params) { if(!$this->login()) return false; //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $put_params; $json_data = json_encode($json_params); $ch = curl_init(); curl_setopt($this->ch, CURLOPT_POST, true ); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Length: '.strlen($json_data), 'Content-Type: application/json', 'Accept: application/json', 'X-Auth-Token: '.$this->auth_token )); return $this->get($ch, $put_params); } private function sendDELETERequest($delete_params) { if(!$this->login()) return false; $parts = explode('?', $delete_params); $url = $parts[0] . ".json"; if(isset($parts[1])) $url .= '?' . $parts[1]; //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url; $ch = curl_init(); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Accept: application/json', 'X-Auth-Token: '.$this->auth_token )); return $this->get($ch, $url); } public function setConfiguration($config) { parent::setConfiguration($config); $this->auth_end_point = $config['endpoint'] == 'US' ? self::US_AUTHURL : self::UK_AUTHURL; $this->api_end_point = $config['endpoint'] == 'US' ? self::US_DNS_ENDPOINT : self::UK_DNS_ENDPOINT; } public function zoneExists() { // $this->getDomainID(); try { $ret = $this->sendGETRequest('/domains?name=' . rawurlencode($this->domain)); } catch (exceptions\DNSSubmoduleException $e) { if($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR) { return false; } throw $e; } if($ret['totalEntries'] == 0) return false; else return true; } private function getDomainID() { $ret = $this->sendGETRequest('/domains?name=' . rawurlencode($this->domain)); $id = $ret['domains'][0]['id']; if(empty($id)) { throw new exceptions\DNSSubmoduleException('Zone does not exist', dns\SubmoduleExceptionCodes::INVALID_RESPONSE); } return $id; } public function getRecords($recordType = false) { $id = $this->getDomainID(); $ret = $this->sendGETRequest('/domains/'.$id.'/records'); $out = array(); foreach($ret['records'] as $r) { $type = strtoupper((string)$r['type']); if(in_array($type, $recordType!==false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes())) { $record = new dns\record\Record(); $record->line = (string)$r['id']; $record->name = (string)$r['name']; $record->ttl = $r['ttl']; $record->type = $type; $record->createRDATAObject($type); switch($type) { case 'SRV': case 'MX': $record->rdata->fromString($r['priority'] . ' ' . $r['data']); break; case 'TXT': $record->rdata->setFirstProperty(html_entity_decode($r['data'])); break; default: $record->rdata->setFirstProperty($r['data']); break; } $out[] = $record; } } return $out; } public function addRecord(dns\record\Record $record) { $id = $this->getDomainID(); $input = $this->recordToInputArray($record); $json_data = array('records' => array($input)); $url = '/domains/'.$id.'/records'; return $this->callbackLoop($this->sendPOSTRequest($url, $json_data)); } private function recordToInputArray($record) { switch($record->type) { case 'MX': $data = $record->rdata->exchange; $priority = $record->rdata->preference; break; case 'SRV': $priority = $record->rdata->priority; $data = $record->rdata->toString(array('weight' => '', 'port' => '', 'target' => '')); break; case 'TXT': $data = $record->rdata->toString(); //trim( . '"'); break; default: $data = $record->rdata->toString(); break; } $input = array( 'name' => $record->nameToAbsolute($this->domain, false), 'type' => $record->type, 'ttl' => $record->ttl, 'data' => $data ); if(isset($priority)) { $input['priority'] = $priority; } return $input; } private function callbackLoop($ret) { $this->checkErrors($ret); $timeout = time() + 20; while(isset($ret['callbackUrl']) && $timeout > time()) { $this->callbacks [] = $ret; usleep(500000); $url = explode('status', $ret['callbackUrl']); $ret = $this->sendGETRequest('/status' . array_pop($url).'?showDetails=true'); if(isset($ret['error'])) { throw new exceptions\DNSSubmoduleException($ret['error']['details'], dns\SubmoduleExceptionCodes::COMMAND_ERROR); } if($ret['status'] == 'COMPLETED') { break; } } if($ret['status'] == 'COMPLETED') { return true; } throw new exceptions\DNSSubmoduleException("Unknown Error", dns\SubmoduleExceptionCodes::COMMAND_ERROR); } public function editRecord(dns\record\Record $record) { $id = $this->getDomainID(); $input = $this->recordToInputArray($record); $input['id'] = $record->line; $json_data = array('records' => array($input)); $url = '/domains/'.$id.'/records'; return $this->callbackLoop($this->sendPUTRequest($url, $json_data)); } public function deleteRecord(dns\record\Record $record) { $id = $this->getDomainID(); return $this->callbackLoop($this->sendDELETERequest('/domains/'.$id.'/records/'.$record->line)); } public function activateZone() { if($this->zoneExists()) { throw new exceptions\DNSSubmoduleException("Zone already exist", dns\SubmoduleExceptionCodes::COMMAND_ERROR); } $json_data = array( 'domains' => array( array( 'name' => $this->domain, 'emailAddress' => 'sample@rackspace.com', 'recordsList' => array('records' => array()) ) ) ); return $this->callbackLoop($this->sendPOSTRequest('/domains', $json_data)); } public function terminateZone() { $id = $this->getDomainID(); return $this->callbackLoop($this->sendDELETERequest('/domains/'.$id.'?deleteSubdomains=true')); } private function getErrorCodeDescription($error_code = "0") { $errors = array ( "400" => "Bad request", "401" => "Bad username or password", "403" => "Resize not allowed", "404" => "Item not found", "409" => "Item already exists", "413" => "Over API limit (check limits())", "415" => "Bad media type", "500" => "Cloud server issue", "503" => "API service in unavailable, or capacity is not available", "0" => "UNKNOWN ERROR" ); return $errors[$error_code]; } private function checkErrors($ret) { if($ret['code'] > 203 && isset($ret['message'])) { throw new exceptions\DNSSubmoduleException($ret['message'] . (isset($ret['details'])?' Details: ' . $ret['details']:''), dns\SubmoduleExceptionCodes::COMMAND_ERROR); } if(isset($ret['validationErrors']['messages']) && count($ret['validationErrors']['messages'])) { throw new exceptions\DNSSubmoduleException(implode('; ', $ret['validationErrors']['messages']), dns\SubmoduleExceptionCodes::COMMAND_ERROR); } } public function getZones($params = false) { $params = $params ? $params : '/domains'; $ret = $this->sendGETRequest($params); $out = array(); foreach($ret['domains'] as $domain) { $out[$domain['name']] = ''; } $nextStep = $ret['links'][0]; if($nextStep['rel'] == 'next') { $fullUrl = $nextStep['href']; list($base, $url) = explode($this->account_id, $fullUrl); $out += $this->getZones($url); } return $out; } }