<?php
/**
* The main class for sending and parsing server requests to the
* GoDaddy?® TotalDNS management system. Eventually this class
* could split into multiple classes representing the various
* components such as the Service, Account, Zone, and Record(s).
*/
class GoDaddyDNS{
/**
* Class variables
*/
private $_config;
private $_curlHandle;
private $_lastResponse;
/**
* Initialize the configuration array with configuration defaults.
*/
public function __construct($config = array()) {
// Apply default configuration settings
$this->_config = array_merge(array(
'username' =>'',
'password' =>'',
'domain' =>'',
'cookie_file' => tempnam(sys_get_temp_dir(), 'Curl'),
'auto_remove_cookie_file' => true,
'auto_logout' => true,
'godaddy_dns_zonefile_url' => 'https://dns.godaddy.com/ZoneFile.aspx?zoneType=0&sa=&zone=',
'godaddy_dns_zonefile_ws_url' => 'https://dns.godaddy.com/ZoneFile_WS.asmx'
), $config);
$this->_authenticate($this->_config["username"],$this->_config["password"],$this->_config["domain"]);
}
/**
* Destroy the curl handle and unlink the cookies file.
*/
public function __destruct() {
if ($this->_config['auto_logout']) {
$this->logout();
}
if ($this->_curlHandle) {
curl_close($this->_curlHandle);
}
if ($this->_config['auto_remove_cookie_file'] && file_exists($this->_config['cookie_file'])) {
unlink($this->_config['cookie_file']);
}
}
/**
* Login to the user's account, returning an error if the credentials are
* invalid or the login fails.
*/
private function _authenticate($username, $password,$domain) {
$this->_lastResponse = $this->_fetchURL($this->_config['godaddy_dns_zonefile_url'].$domain);
if (!$this->isLoggedIn($username)) {
// User is not already logged in, build and submit a login request
$postUrl = curl_getinfo($this->_curlHandle, CURLINFO_EFFECTIVE_URL);
$post = array(
'Login$userEntryPanel2$LoginImageButton.x' => 0,
'Login$userEntryPanel2$LoginImageButton.y' => 0,
'Login$userEntryPanel2$UsernameTextBox' => $username,
'Login$userEntryPanel2$PasswordTextBox' => $password,
'__EVENTARGUMENT' => $this->_getField('__EVENTARGUMENT'),
'__EVENTTARGET' => $this->_getField('__EVENTTARGET'),
'__VIEWSTATE' => $this->_getField('__VIEWSTATE'),
);
$this->_lastResponse = $this->_fetchURL($postUrl, $post);
if (!$this->isLoggedIn($username, $this->_lastResponse)) {
// Invalid username/password or unknown response received
return false;
}
}
return true;
}
/**
* Check to see if the expected user is logged in.
*/
public function isLoggedIn($username) {
if (preg_match('#Welcome: <span id="ctl00_lblUser" .*?\>(.*)</span>#', $this->_lastResponse, $match)) {
if (strtolower($match[1]) == strtolower($username) || $match[2] == $username) {
return true;
} else {
// An unexpected user was logged in
$this->logout();
}
}
return false;
}
/**
* Log the user out.
*/
public function logout() {
if (preg_match('#<a [^>]+href="(.*?)"[^>]*>Log Out</a>#', $this->_lastResponse, $match)) {
$this->_lastResponse = $this->_fetchURL($match[1]);
if (preg_match('#<img src="([^"]+)" height="1" width="1" />#', $this->_lastResponse, $match)) {
$this->_lastResponse = $this->_fetchURL($match[1]);
return true;
}
}
return false;
}
/**
* Add new record
*/
public function AddRecord($host,$type = 'A',$pointsTo,$ttl=3600){
$domain=$this->_config["domain"];
$next_record_id=$this->_nextRecordIndex();
switch (strtoupper($type)) {
case 'A':
$post = array(
'sInput' => '<PARAMS>
<PARAM name="host" value="'.$host.'" />
<PARAM name="pointsTo" value="'.$pointsTo.'" />
<PARAM name="lstIndex" value="'.$next_record_id.'" />
<PARAM name="ttl" value="'.$ttl.'" />
</PARAMS>',
);
$calloutResponse = $this->_fetchURL($this->_config['godaddy_dns_zonefile_ws_url'] . '/AddNewARecord', http_build_query($post, '', '&'));
if (strpos($calloutResponse, 'SUCCESS') === false) {
return false;
}
// Commit the updates
$post = array(
'sInput' => '<PARAMS>
<PARAM name="domainName" value="' . $domain . '" />
<PARAM name="zoneType" value="0" />
<PARAM name="aRecEditCount" value="1" />
<PARAM name="aRecEdit0Index" value="'.$next_record_id.'" />
<PARAM name="aRecDeleteCount" value="0" />
<PARAM name="cnameRecEditCount" value="0" />
<PARAM name="cnameRecDeleteCount" value="0" />
<PARAM name="mxRecEditCount" value="0" />
<PARAM name="mxRecDeleteCount" value="0" />
<PARAM name="txtRecEditCount" value="0" />
<PARAM name="txtRecDeleteCount" value="0" />
<PARAM name="srvRecEditCount" value="0" />
<PARAM name="srvRecDeleteCount" value="0" />
<PARAM name="aaaaRecEditCount" value="0" />
<PARAM name="aaaaRecDeleteCount" value="0" />
<PARAM name="soaRecEditCount" value="0" />
<PARAM name="soaRecDeleteCount" value="0" />
<PARAM name="nsRecEditCount" value="0" />
<PARAM name="nsRecDeleteCount" value="0" />
</PARAMS>',
);
$calloutResponse = $this->_fetchURL($this->_config['godaddy_dns_zonefile_ws_url'] . '/SaveRecords', http_build_query($post, '', '&'));
if (strpos($calloutResponse, 'SUCCESS') === false) {
return false;
}
return true;
default:
// Other record types are currently unsupported
throw new Exception('Unknown record type encountered: ' . $type);
}
}
/**
* Delete record
*/
public function deleteRecord($record){
$host=$record["host"];
$domain=$this->_config["domain"];
switch (strtoupper($record["type"])) {
case 'A':
$post = array(
'sInput' => $record['index'].'|true',
);
$calloutResponse = $this->_fetchURL($this->_config['godaddy_dns_zonefile_ws_url'] . '/FlagARecForDeletion', http_build_query($post, '', '&'));
if (strpos($calloutResponse, 'SUCCESS') === false) {
return false;
}
// Commit the updates
$post = array(
'sInput' => '<PARAMS>
<PARAM name="domainName" value="' . $domain . '" />
<PARAM name="zoneType" value="0" />
<PARAM name="aRecEditCount" value="0" />
<PARAM name="aRecDeleteCount" value="1" />
<PARAM name="aRecDelete0Index" value="' . $record['index'] . '" />
<PARAM name="cnameRecEditCount" value="0" />
<PARAM name="cnameRecDeleteCount" value="0" />
<PARAM name="mxRecEditCount" value="0" />
<PARAM name="mxRecDeleteCount" value="0" />
<PARAM name="txtRecEditCount" value="0" />
<PARAM name="txtRecDeleteCount" value="0" />
<PARAM name="srvRecEditCount" value="0" />
<PARAM name="srvRecDeleteCount" value="0" />
<PARAM name="aaaaRecEditCount" value="0" />
<PARAM name="aaaaRecDeleteCount" value="0" />
<PARAM name="soaRecEditCount" value="0" />
<PARAM name="soaRecDeleteCount" value="0" />
<PARAM name="nsRecEditCount" value="0" />
<PARAM name="nsRecDeleteCount" value="0" />
</PARAMS>',
);
$calloutResponse = $this->_fetchURL($this->_config['godaddy_dns_zonefile_ws_url'] . '/SaveRecords', http_build_query($post, '', '&'));
if (strpos($calloutResponse, 'SUCCESS') === false) {
return false;
}
return true;
case 'CNAME':
case 'MX':
case 'TXT':
case 'SRV':
case 'AAAA':
case 'NS':
default:
// Other record types are currently unsupported
throw new Exception('Unknown record type encountered: ' . $type);
}
}
/**
* Find and return the details about a host record, return false if nothing is found.
*
* Note: The only type of records currently supported are "A" records.
*/
public function findRecords($host,$type = 'A') {
$domain=strtolower($this->_config["domain"]);
$currentZone = $this->_getField('ctl00$cphMain$hdnCurrentZone');
if (strtolower($currentZone) != strtolower($domain)) {
// Request zone details if not already loaded -
// could keep a separate cache of each zone's records in the future
$this->_lastResponse = $this->_fetchUrl($this->_config['godaddy_dns_zonefile_url'] . $domain);
}
$records=array();
$offset=0;
while(preg_match("#Undo{$type}Edit\('tbl{$type}Records_([0-9]+)?', '({$host})', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?'\);#is", $this->_lastResponse, $match,0,$offset)) {
array_push($records,array_combine(array('match', 'index', 'host', 'data', 'ttl', 'host_td', 'points_to', 'rec_modified','type'), array_merge($match,array($type))));
$offset=strpos($this->_lastResponse,$match[0],$offset)+strlen($match[0]);
}
return $records;
}
private function _nextRecordIndex($type = 'A'){
return preg_match_all("#Undo{$type}Edit\('tbl{$type}Records_([0-9]+)?', '([^']+)', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?'\);#is", $this->_lastResponse, $match,0,$offset);
}
/**
* Connect to the remote server using CURL.
*/
private function _fetchURL($url, $post = null, $referer = '', $agent = 'Mozilla/5.0 (compatible; PHP; cURL)', $language = 'en', $timeout = 30) {
// Initialize CURL
if (!$this->_curlHandle) {
if (!function_exists('curl_init')) {
die('CURL is not loaded or compiled into this version of PHP.');
}
if (!is_writable($this->_config['cookie_file'])) {
die('Cookie jar file is not writable: ' . $this->_config['cookie_file']);
}
$this->_curlHandle = curl_init();
curl_setopt_array($this->_curlHandle, array(
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_AUTOREFERER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_COOKIEJAR => $this->_config['cookie_file'],
CURLOPT_COOKIEFILE => $this->_config['cookie_file'],
));
}
// Set the options
curl_setopt($this->_curlHandle, CURLOPT_URL, $url);
curl_setopt($this->_curlHandle, CURLOPT_REFERER, $referer);
curl_setopt($this->_curlHandle, CURLOPT_USERAGENT, $agent);
$extraHeaders = array(
'Accept-Language: ' . $language,
);
curl_setopt($this->_curlHandle, CURLOPT_HTTPHEADER, $extraHeaders);
if ($post) {
curl_setopt($this->_curlHandle, CURLOPT_POST, true);
curl_setopt($this->_curlHandle, CURLOPT_POSTFIELDS, $post);
} else {
curl_setopt($this->_curlHandle, CURLOPT_HTTPGET, true);
}
// Execute the request, returning the results
return curl_exec($this->_curlHandle);
}
/**
* Parse and return a named field's value from the last response.
*/
private function _getField($name) {
if (preg_match_all('#<input[^>]+>#is', $this->_lastResponse, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$fieldHtml = $match[0];
if ($this->_getFieldAttribute('name', $fieldHtml) == $name) {
return $this->_getFieldAttribute('value', $fieldHtml);
}
}
}
return false;
}
/**
* Get the attribute from a field's html.
*/
private function _getFieldAttribute($attribute, $fieldHtml) {
if (preg_match('#' . $attribute . '=["\']([^"\']+)?["\']#is', $fieldHtml, $match)) {
return $match[1];
}
return false;
}
}
?>