Shabat Closer

Wednesday, April 17, 2013

PHP: GODADDY Class - Change Godaddy DNS record Script

PHP Godaddy Class  - script to add/delete record from godaddy dns.

<?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:&nbsp;<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;
 }
}
?>


Usage: Add record

<?
$dns = new GoDaddyDNS(array(
 "username"=>'username',
 "password"=>'password',
 'domain'  =>'domain.com'
));
$dns->AddRecord("@","A","123.123.123.123",3600);
?>

Usage: Delete record

<?php
$dns = new GoDaddyDNS(array(
 "username"=>'username',
 "password"=>'password',
 'domain'  =>'domain.com'
));
$records = $dns->findRecords("@");
foreach ($records as $record){
 if ($record["data"]=="123.123.123.123"){
  $dns->deleteRecord($record);
 }
}
$dns->deleteRecord($record);
?>


4 comments:

  1. This is very good man, but only works up to the first 50 domains. This is how many you get per page.
    $next_record_id always returns 50, and domains beyond 50 are not being added...

    Marcin

    ReplyDelete
    Replies
    1. first 50 SUBdomains I meant, sorry.

      Delete
  2. Here's my fix. Bear in mind deleting records, if you have more than 50, may show the same issue. This fixes adding domains:

    private function _nextRecordIndex($type = 'A') {
    preg_match("#;n{$type}RecordCount=(\d+);#is", $this->_lastResponse, $match);
    return intval($match[1]);
    // return preg_match_all("#Undo{$type}Edit\('tbl{$type}Records_([0-9]+)?', '([^']+)', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?', '([^']+)?'\);#is", $this->_lastResponse, $match,0,$offset);
    }

    ReplyDelete
  3. Very Good Class. But Authenstication method returns false. Although my username and password are correct (the same I use to login to godaddy.com).
    Is there something changed? How can I solve this.

    ReplyDelete