All pastes #978085 Raw Edit

Something

public text v1 · immutable
·published 2008-04-09 08:52 UTC
rendered paste body
<?

// DNS Administration Class - Copyright 2008 Entorno Digital 
// ---------------------------------------------------------
//
// Written by Miles Barry aka El Diablo
// 
// The class enables easy and transparent editing of DNS zones
// and acts as a middle man between the application and server
// by providing simple to use and scaleable set of functions
// that requires no knowledge of the dns servers workings.
//
// How it works:
// ------------
//
// The application makes remote zone requests and transfers
// via a TCP/IP connection. All data passed to a from is in a
// predefined XML structure. The application class parses the
// XML to an array, which the application can then administer
// via the available functions. When the application wants to
// update the zone on the remote server, it simply performs a
// save, which converts the array back to the XML structure
// makes a TCP/IP connection to the server where the dns zone
// came from, sends the XML zone data. The remote server will
// then parse the XML data, converting it into the correct
// backend data structure and update the zone.
//
// {Application}<->[XML] <-- (TCP/IP) --> [XML]<->{DNS Server}
//
// Report bugs to miles@entorno.es
//
// Notes:
// -----
// Things to include would be IPv6 addressing record management
//
// Usefull DNS RFC's etc:
// ---------------------
// http://www.iana.org/assignments/dns-parameters
// http://tools.ietf.org/html/rfc1912
// http://www.ietf.org/rfc/rfc1035.txt


define ("DNSADMIN_MODE",		"client");

// Defines for the daemon

define ("DNSADMIN_DAEMON_IP",		"0.0.0.0");
define ("DNSADMIN_DAEMON_PORT",		"25353");
define ("DNSADMIN_BACKEND",		"ldap");
define ("DNSADMIN_SOCKET_MAX_BYTES",	16384);

// Defines for OpenLDAP (Used with PowerDNS) backend support

define ("DNSADMIN_LDAP_SERVER",		"127.0.0.1");
define ("DNSADMIN_LDAP_PORT",		"389");
define ("DNSADMIN_LDAP_BASEDN",		"dc=ldap,dc=dit");
define ("DNSADMIN_LDAP_DNS_DN",		"ou=dns,".DNSADMIN_LDAP_BASEDN);
define ("DNSADMIN_LDAP_USERDN",		"cn=pdns,ou=localhost,ou=domains,".DNSADMIN_LDAP_BASEDN);
define ("DNSADMIN_LDAP_USERPW",		'secret');

// Defines for MySQL (Used with PowerDNS) backend support

// Defines for BIND backend support



// ---------------------------- Start of dnsZone Class -------------------------

class dnsZone {

	// Here we store our zone data array

	private $zone = array ();

	// Here we store info on the server

	private $zone_server = NULL;
	private $zone_server_port = NULL;

	// **************************************************************
	// *			Class Constructor			*
	// **************************************************************

	function __construct ($server = 'localhost', $port = DNSADMIN_DAEMON_PORT)

	{

		$this->zone_server = $server;
		$this->zone_server_port = $port;

	}

	// **************************************************************
	// *		Setup the zone to be a new, empty zone		*
	// **************************************************************

//	function setup ($domain)

	// **************************************************************
	// *		   Convert a hostname to a FQDN			*
	// **************************************************************

	function fqdn ($host = '')

	{

		$host = trim ($host);

		if (!isset ($this->zone['name'])) return FALSE;

		if (substr ($host, - (strlen ($this->zone['name']))) === $this->zone['name']) return $host;

		if ($host === '') return $this->zone['name'];

		return $host.'.'.$this->zone['name'];

	}

	// **************************************************************
	// *			  Remove empty host			*
	// **************************************************************

	function remove_empty_host ($host)

	{

		$host = $this->fqdn ($host);

		if (count ($this->zone['hosts'][$host]) === 0) unset ($this->zone['hosts'][$host]);

	}

	// **************************************************************
	// *		    Check if host has an a record		*
	// **************************************************************

	function has_a ($host = '')

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (is_array ($this->zone['hosts'][$host]['arecord'])) return TRUE;

		return FALSE;

	}

	// **************************************************************
	// *		Check if the host has a cname record		*
	// **************************************************************

	function has_cname ($host = '')

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (isset ($this->zone['hosts'][$host]['cnamerecord'])) return TRUE;

	}

	// **************************************************************
	// *			     Get Zona Name			*
	// **************************************************************

	function get_zone_name ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return $this->zone['name'];

	}

	// **************************************************************
	// *			     Set Zone Name			*
	// **************************************************************

	function set_zone_name ($name)

	{

		$this->zone['name'] = $name;

	}

	// **************************************************************
	// *			   Get a hosts TTL			*
	// **************************************************************

	function get_ttl ($host = '')

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (isset ($this->zone['hosts'][$host]['dnsttl'])) return $this->zone['hosts'][$host]['dnsttl'];

		return FALSE;

	}

	// **************************************************************
	// *			Set a hosts TTL				*
	// **************************************************************

	function set_ttl ($host = '', $ttl = 3600)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		// Ensure a numeric value is passed

		if (!is_numeric ($ttl)) return FALSE;

		$host = $this->fqdn ($host);

		// Don't permit TTL values more than one week (60*60*24*7) = 604800

		if ($ttl > 604800) return FALSE;

		$this->zone['hosts'][$host]['dnsttl'] = $ttl;
		return TRUE;		

	}

	// **************************************************************
	// *			update the SOA SERIAL			*
	// **************************************************************

	function update_soa_serial ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$new_serial = date ('Ymd')."00";

		if (strncmp ($this->zone['soa']['serial'], $new_serial, 8) === 0) $new_serial = date ('Ymd').(substr ($this->zone['soa']['serial'], -2) +1);

		return $new_serial;

	}

	// **************************************************************
	// *			Get the SOA SERIAL			*
	// **************************************************************

	function get_soa_serial ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if (!isset ($this->zone['soa']['serial'])) $this->zone['soa']['serial'] = '00000000';

		return ($this->update_soa_serial());

	}

	// **************************************************************
	// *			Get the SOA REFRESH			*
	// **************************************************************

	function get_soa_refresh ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return ($this->zone['soa']['refresh']);

	}

	// **************************************************************
	// *			Set the SOA REFRESH			*
	// **************************************************************

	function set_soa_refresh ($refresh = 86400)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if (!is_numeric ($refresh)) return FALSE;

		$this->zone['soa']['refresh'] = $refresh;

		return TRUE;

	}

	// **************************************************************
	// *			Get the SOA RETRY			*
	// **************************************************************

	function get_soa_retry ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return ($this->zone['soa']['retry']);

	}

	// **************************************************************
	// *			Set the SOA RETRY			*
	// **************************************************************

	function set_soa_retry ($retry = 86400)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if (!is_numeric ($retry)) return FALSE;

		$this->zone['soa']['retry'] = $retry;

                return TRUE;

	}

	// **************************************************************
	// *			Get the SOA EXPIRE			*
	// **************************************************************

	function get_soa_expire ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return ($this->zone['soa']['expire']);

	}

	// **************************************************************
	// *			Set the SOA EXPIRE			*
	// **************************************************************

	function set_soa_expire ($expire = 604800)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if (!is_numeric ($expire)) return FALSE;

		$this->zone['soa']['expire'] = $expire;

	}

	// **************************************************************
	// *			Get the SOA RNAME			*
	// **************************************************************

	function get_soa_rname ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return ($this->zone['soa']['rname']);

	}

	// **************************************************************
	// *			Set the SOA RNAME			*
	// **************************************************************

	function set_soa_rname ($rname = NULL)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if ($rname == NULL) $rname = 'hostmaster@'.$this->zone['name'];
		$this->zone['soa']['rname'] = $rname;

	}

	// **************************************************************
	// *			Get the SOA MNAME			*
	// **************************************************************

	function get_soa_mname ()

	{

		if (!isset ($this->zone['name'])) return FALSE;

		return ($this->zone['soa']['mname']);

	}

	// **************************************************************
	// *			Set the SOA MNAME			*
	// **************************************************************

	function set_soa_mname ($mname)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$this->zone['soa']['mname'] = $mname;

		return TRUE;

	}

	// **************************************************************
	// *		   	Add a CNAME record			*
	// **************************************************************

	function add_cname ($cname, $server)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$cname = $this->fqdn ($cname);

		if ($this->has_a ($cname)) return FALSE;

		$this->zone['hosts'][$cname]['cnamerecord'][0] = $server;

		return TRUE;

	}

	// **************************************************************
	// *			Delete a CNAME record			*
	// **************************************************************

	function del_cname ($cname)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$cname = $this->fqdn ($cname);

		if (!$this->has_cname ($cname)) return FALSE;

		unset ($this->zone['hosts'][$cname]);

		return TRUE;

	}

	// **************************************************************
	// *			    Add an A record			*
	// **************************************************************

	function add_a ($host, $ip)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if (!ip2long ($ip)) return false;

		$host = $this->fqdn ($host);

		if ($this->has_cname ($host)) return FALSE;

		if (!is_array ($this->zone['hosts'][$host]['arecord'])) $this->zone['hosts'][$host]['arecord'] = array ();

		array_push ($this->zone['hosts'][$host]['arecord'], $ip);

		return TRUE;

	}

	// **************************************************************
	// *			   Delete an A record			*
	// **************************************************************

	function del_a ($host)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (!$this->has_a ($host)) return FALSE;

		unset ($this->zone['hosts'][$host]['arecord']);

		$this->remove_empty_host ($host);

		return TRUE;

	}

	// **************************************************************
	// *			   Add an NS record			*
	// **************************************************************

	function add_ns ($host = '', $ns)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (!is_array ($this->zone['hosts'][$host]['nsrecord'])) $this->zone['hosts'][$host]['nsrecord'] = array (); 

		if (!in_array ($ns, $this->zone['hosts'][$host]['nsrecord'])) array_push ($this->zone['hosts'][$host]['nsrecord'], $ns);

		return TRUE;

	}

	// **************************************************************
	// *			Delete an NS record			*
	// **************************************************************

	function del_ns ($host = '', $ns)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		$results = array_keys  ($this->zone['hosts'][$host]['nsrecord'], $ns);

		foreach ($results as $delete) unset ($this->zone['hosts'][$host]['nsrecord'][$delete]);

		$this->zone['hosts'][$host]['nsrecord'] = array_values ($this->zone['hosts'][$host]['nsrecord']);

		if (count ($this->zone['hosts'][$host]['nsrecord']) === 0) unset ($this->zone['hosts'][$host]['nsrecord']);

		return TRUE;

	}

	// **************************************************************
	// *			Add an MX record			*
	// **************************************************************

	function add_mx ($host = '', $server, $priority)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if ((!is_string ($server)) || (!is_numeric ($priority))) return FALSE;

		$host = $this->fqdn ($host);

		if (!is_array ($this->zone['hosts'][$host]['mxrecord'][$priority])) $this->zone['hosts'][$host]['mxrecord'][$priority] = array();

		array_push ($this->zone['hosts'][$host]['mxrecord'][$priority], $server);

		ksort ($this->zone['hosts'][$host]['mxrecord']);

		return TRUE;

	}

	// **************************************************************
	// *			Delete an MX record			*
	// **************************************************************

	function del_mx ($host = '', $server, $priority)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		if ((!is_string ($server)) || (!is_numeric ($priority))) return FALSE;

		$host = $this->fqdn ($host);

		$results = array_keys  ($this->zone['hosts'][$host]['mxrecord'][$priority], $server);

		unset ($this->zone['hosts'][$host]['mxrecord'][$priority][$results[0]]);

		if (count ($this->zone['hosts'][$host]['mxrecord'][$priority]) === 0) unset ($this->zone['hosts'][$host]['mxrecord'][$priority]);

		if (count ($this->zone['hosts'][$host]['mxrecord']) === 0) unset ($this->zone['hosts'][$host]['mxrecord']);

		ksort ($this->zone['hosts'][$host]['mxrecord']);

		return TRUE;

	}

	// **************************************************************
	// *			   Add a TXT record			*
        // **************************************************************

	function add_txt ($host = '', $string)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$host = $this->fqdn ($host);

		if (!is_array ($this->zone['hosts'][$host]['txtrecord'])) $this->zone['hosts'][$host]['txtrecord'] = array ();

		if (!in_array ($string, $this->zone['hosts'][$host]['txtrecord'])) array_push ($this->zone['hosts'][$host]['txtrecord'], $string);

	}

        // **************************************************************
        // *                    Delete a TXT record                     *
        // **************************************************************

        function del_txt ($string)

	{

		if (!isset ($this->zone['name'])) return FALSE;

		$results = array_keys  ($this->zone['txt'], $string);
		foreach ($results as $delete) unset ($this->zone['txt'][$delete]);

		$this->zone['txt'] = array_values ($this->zone['txt']);
		if (count ($this->zone['txt']) == 0) unset ($this->zone['txt']);

	}

	// **************************************************************
	//*	   Get remotely a given domain in XML format		*
	// **************************************************************

	function get ($domain)

	{

		$socket = new dnsSocket ();

		// Establish the connection to the zone server

		$socket->connect ($this->zone_server, $this->zone_server_port);

		// Request the XML data of the required domain

		$socket->write ('getzone:'.$domain."\n");

		// Get the XML data respons

		$xml = $socket->read ();

		$this->to_raw ($xml);

		// Close the socket

		$socket->close ();

	}

	// **************************************************************
	// *			Get raw zone data			*
	// **************************************************************

	function get_raw ($domain)

	{

		if (DNSADMIN_BACKEND == 'ldap') {

			$this->get_LDAP ($domain);
			return TRUE;

		}

		// No compatible backend

		return FALSE;

	}

	// **************************************************************
	// *			Set raw zone data			*
	// **************************************************************

	function set_raw ()

	{

		if (DNSADMIN_BACKEND == 'ldap') {

			$this->set_LDAP ();
			return TRUE;

		}

		// No compatible backend

		return FALSE;

	}

	// **************************************************************
	// *			  Get raw all zones			*
	// **************************************************************

	function get_all_zones ()

	{

		if (DNSADMIN_BACKEND == 'ldap') {

			$ldapconn = ldap_connect (DNSADMIN_LDAP_SERVER, DNSADMIN_LDAP_PORT) or die ("Could not connect to $ldaphost");
			$ldapbind = ldap_bind ($ldapconn);

			$filter = array ('associatedDomain');

			$sr = @ldap_search ($ldapconn, $zone_dn, '(sOARecord=*)', $filter);

			if ($sr === FALSE) {

				ldap_unbind ($ldapconn);
				return FALSE;
			}

			$info = ldap_get_entries ($ldapconn, $sr);

			$results = array ();

			for ($n = 0; $n < $info['count']; $n++) array_push ($results, $info[$n]['associateddomain'][0]);

			ldap_unbind ($ldapconn);

			return ($results);

		}

		return FALSE;

	}

	// **************************************************************
	// *		Get a given domain from LDAP backend		*
	// **************************************************************

	function get_LDAP ($domain)

	{

		// Convert the domain name to a DN

		$zone_dn = 'dc='.str_replace ('.', ',dc=', $domain).','.DNSADMIN_LDAP_DNS_DN;

		$ldapconn = ldap_connect (DNSADMIN_LDAP_SERVER, DNSADMIN_LDAP_PORT) or die ("Could not connect to $ldaphost");
		$ldapbind = ldap_bind ($ldapconn);

		$filter = array ('associatedDomain',
				 'cNAMERecord',
				 'mXRecord',
				 'nSRecord',
				 'sOARecord',
				 'tXTRecord',
				 'aRecord',
				 'dNSTTL',
				 'dc',
				);
 
		$sr = @ldap_search ($ldapconn, $zone_dn, '(objectClass=dNSDomain)', $filter);

		if ($sr === FALSE) {

			ldap_unbind ($ldapconn);
			$this->zone = array();
			return FALSE;

		}

		$info = ldap_get_entries ($ldapconn, $sr);

		ldap_unbind ($ldapconn);

		$this->zone = array ();

		// Work out the zone name from the host having the SOA record (Can this be changed to a search??)

		foreach ($info as $a=>$b) {

			if (is_array ($b['soarecord'])) {

				$this->zone['name'] = $b['associateddomain'][0];
				break;

			}

		}

		// Tidy up hosts and records

		for ($n = 0; $n < $info['count']; $n++) {

			$host = $info[$n]['associateddomain'][0];

			$this->zone['hosts'][$host] = array();

			for ($o = 0; $o < $info[$n]['count']; $o++) {

				// Now process the registers

				switch ($info[$n][$o]) {

				// SOA

				case "soarecord":

					list ($this->zone['soa']['mname'], $this->zone['soa']['rname'], $this->zone['soa']['serial'], $this->zone['soa']['refresh'], $this->zone['soa']['retry'], $this->zone['soa']['expire']) = split (' ', $info[$n]['soarecord'][0]);

				break;

				// A

				case "arecord":

					$this->zone['hosts'][$host]['arecord'] = array ();

					for ($p = 0; $p < $info[$n]['arecord']['count']; $p++) array_push ($this->zone['hosts'][$host]['arecord'], $info[$n]['arecord'][$p]);

				break;				

				// TTL

				case "dnsttl":

					$this->zone['hosts'][$host]['dnsttl'] = $info[$n]['dnsttl'][0];

				break;

				// CNAME

				case "cnamerecord":

					$this->zone['hosts'][$host]['cnamerecord'] = $info[$n]['cnamerecord'][0];

				break;

				// TXT

				case "txtrecord":

					$this->zone['hosts'][$host]['txtrecord'] = array ();
	
					for ($p = 0; $p < $info[$n]['txtrecord']['count']; $p++) array_push ($this->zone['hosts'][$host]['txtrecord'], $info[$n]['txtrecord'][$p]);

				break;

				// NS

				case "nsrecord":

					$this->zone['hosts'][$host]['nsrecord'] = array ();

					for ($p = 0; $p < $info[$n]['nsrecord']['count']; $p++) array_push ($this->zone['hosts'][$host]['nsrecord'], $info[$n]['nsrecord'][$p]);

				break;

				// MX

				case "mxrecord":

					$this->zone['hosts'][$host]['mxrecord'] = array ();

					for ($p = 0; $p < $info[$n]['mxrecord']['count']; $p++) {

						list ($mx_priority, $mx_server) = split(" ", $info[$n]['mxrecord'][$p]);

						if (!isset ($this->zone['hosts'][$host]['mxrecord'][$mx_priority])) $this->zone['hosts'][$host]['mxrecord'][$mx_priority] = array ();

						array_push ($this->zone['hosts'][$host]['mxrecord'][$mx_priority], $mx_server);

						//$this->add_mx ($mx_server, $mx_priority);

					}

				break;

				// Unknown

				default:

				break;

				}

			}
			

		}

	}

	// **************************************************************
	// *		Set a given domain in a LDAP backend		*
	// **************************************************************

	function set_LDAP ()

	{

		$ldapconn = ldap_connect (DNSADMIN_LDAP_SERVER, DNSADMIN_LDAP_PORT) or die ("Could not connect to $ldaphost");

		$ldapbind = ldap_bind ($ldapconn, DNSADMIN_LDAP_USERDN, DNSADMIN_LDAP_USERPW);

		if ($ldapbind === FALSE) {

			echo 'Bind: Failed'."\n";

			ldap_unbind ($ldapconn);

			return FALSE;

		}

		foreach ($this->zone['hosts'] as $hostname=>$data) {

			$zone_dn = 'dc='.str_replace ('.', ',dc=', $hostname).','.DNSADMIN_LDAP_DNS_DN;

			$modify = array ();

			// Modify the SOA

			if ($hostname === $this->zone['name']) {

				$soa = $this->get_soa_mname()." ";
				$soa.= $this->get_soa_rname()." ";
				$soa.= $this->get_soa_serial()." ";
				$soa.= $this->get_soa_refresh()." ";
				$soa.= $this->get_soa_retry()." ";
				$soa.= $this->get_soa_expire();
				$modify['sOARecord'] = $soa;

			}

			// Modify any A records

			$modify['aRecord'] = array ();
			if (is_array ($this->zone['hosts'][$hostname]['arecord'])) $modify['aRecord'] = $this->zone['hosts'][$hostname]['arecord'];

			// Modify any CNAME records

			$modify['cNAMERecord'] = array ();
			if (is_array ($this->zone['hosts'][$hostname]['cnamerecord'])) $modify['cNAMERecord'] = $this->zone['hosts'][$hostname]['cnamerecord'];

			// Modify any NS records

			$modify['nSRecord'] = array ();
			if (is_array ($this->zone['hosts'][$hostname]['nsrecord'])) $modify['nSRecord'] = $this->zone['hosts'][$hostname]['nsrecord'];

			// Modify any MX records

			$modify['mXRecord'] = array ();

			if (is_array ($this->zone['hosts'][$hostname]['mxrecord'])) foreach ($this->zone['hosts'][$hostname]['mxrecord'] as $priority=>$servers) foreach ($servers as $server) array_push ($modify['mXRecord'], "$priority $server");

			// Modify any TXT records

			$modify['tXTRecord'] = array ();

			if (is_array ($this->zone['hosts'][$hostname]['txtrecord'])) $modify['tXTRecord'] = $this->zone['hosts'][$hostname]['txtrecord'];

			// Preform the modifications

			if (!@ldap_modify ($ldapconn, $zone_dn, $modify)) {

				$modify['associatedDomain'] = $hostname;
				$modify['dc'] = substr ($hostname, 0, (strpos ($hostname,'.')) );
				$modify['objectClass'][0] = 'dNSDomain2';
				$modify['objectClass'][1] = 'domainRelatedObject';
				$modify['objectClass'][2] = 'top';

				$modify = array_filter ($modify);

				ldap_add ($ldapconn, $zone_dn, $modify);

			}

		}

		ldap_unbind ($ldapconn);

		return TRUE;

	}

	// **************************************************************
	// *		Returns the raw array data of the domain	*
	// **************************************************************

	function raw ()

	{

		if (!isset ($this->zone['name'])) return FALSE;
		return ($this->zone);


	}

	// **************************************************************
	// *		 Returns the zone data XML format		*
	// **************************************************************

	function to_xml ()

	{

		if (!isset ($this->zone['name'])) return '<zone/>'."\n";

		// Set the zone name tag

		$xml = '<zone name="'.$this->zone['name'].'" >'."\n";

		// Set the SOA tag and attributes

		$xml.= ' <soa mname="'.$this->zone['soa']['mname'].'" refresh="'.$this->zone['soa']['refresh'].'" retry="'.$this->zone['soa']['retry'].'" expire="'.$this->zone['soa']['expire'].'" rname="'.$this->zone['soa']['rname'].'" />'."\n";

		foreach ($this->zone['hosts'] as $name=>$data) {

			$xml.= ' <host name="'.$name.'" >'."\n";

				foreach ($data as $record=>$record_data) {

					switch ($record) {

					case "arecord":

						foreach ($record_data as $a_record) $xml.= '  <a ip="'.$a_record.'" />'."\n";

						break;

					case "cnamerecord":

						$xml.= '  <cname server="'.$record_data[0].'" />'."\n";

						break;

					case "nsrecord":

						foreach ($record_data as $ns_record) $xml.= '  <ns server="'.$ns_record.'" />'."\n";

						break;

					case "mxrecord":

						foreach ($record_data as $priority=>$servers) {

							foreach ($servers as $server) $xml.= '  <mx priority="'.$priority.'" server="'.$server.'" />'."\n";

						}

						break;

					case "txtrecord":

						foreach ($record_data as $txt_record) $xml.= '  <txt string="'.$txt_record.'" />'."\n";

						break;

					case "dnsttl":

						$xml.= '  <ttl seconds="'.$record_data.'" />'."\n";

					}		

				}

			$xml.= ' </host>'."\n";

		}

		// Close the zone

		$xml.= '</zone>'."\n";

		return $xml;

	}

	// **************************************************************
	// * 	   Converts the passed XML data to raw array data	*
	// **************************************************************

	function to_raw ($xml)

	{

		$xml_parser = xml_parser_create();

		xml_parse_into_struct ($xml_parser, $xml, $vals, $index);		

		xml_parser_free ($xml_parser);

		// Set the zone name

		$this->zone['name'] = $vals[0]['attributes']['NAME'];

		$this->zone['hosts'] = array ();

		$hostname = NULL;

		foreach ($vals as $xml_elem) {

			if (($xml_elem['type'] === 'open') && ($xml_elem['tag'] === 'HOST')) $hostname = $xml_elem['attributes']['NAME'];

			if (($xml_elem['type'] === 'close') && ($xml_elem['tag'] === 'HOST')) $hostname = NULL;

			if ($xml_elem['type'] === 'complete') {

				switch ($xml_elem['tag']) {

				case "SOA":

					if (count ($xml_elem['attributes']) < 5) break;

					$this->set_soa_mname ($xml_elem['attributes']['MNAME']);
					$this->set_soa_refresh ($xml_elem['attributes']['REFRESH']);
					$this->set_soa_retry ($xml_elem['attributes']['RETRY']);
					$this->set_soa_expire ($xml_elem['attributes']['EXPIRE']);
					$this->set_soa_rname ($xml_elem['attributes']['RNAME']);

					break;

				case "NS":

					if ((isset ($xml_elem['attributes']['SERVER']) === TRUE)) $this->add_ns ($hostname, $xml_elem['attributes']['SERVER']);

					break;

				case "MX":

					if ((isset ($xml_elem['attributes']['PRIORITY']) === TRUE) && (isset ($xml_elem['attributes']['SERVER']) === TRUE)) $this->add_mx ($hostname, $xml_elem['attributes']['SERVER'], $xml_elem['attributes']['PRIORITY']);

					break;

				case "CNAME":

					if (isset ($xml_elem['attributes']['SERVER']) === TRUE) {

						$this->add_cname ($hostname, $xml_elem['attributes']['SERVER']);

						break;

					}


				case "A":

					if (isset ($xml_elem['attributes']['IP']) === TRUE) {

						$this->add_a ($hostname, $xml_elem['attributes']['IP']);

						break;

					}


				case "TXT":

					if ((isset ($xml_elem['attributes']['STRING']) === TRUE)) $this->add_txt ($hostname, $xml_elem['attributes']['STRING']);

				}


			}

		}

	}

// --------------------------- End of dnsZone Class ---------------------------

}

// ------------------------- Start of dnsSocket Class -------------------------

class dnsSocket {

	// Class variables

	private $socket = NULL;
	private $message_socket = NULL;
	private $message_socket_ip = NULL;
	private $error = NULL;

	// **************************************************************
	// *		  Create a new TCP stream socket		*
	// **************************************************************

	function __construct ()

	{

		$this->socket = @socket_create (AF_INET, SOCK_STREAM, SOL_TCP);

		if ($this->socket === FALSE) {

			$this->error = "socket_create() failed: reason: " . socket_strerror (socket_last_error()) . "\n";
			return FALSE;

		}

		if (!socket_set_option ($this->socket, SOL_SOCKET, SO_REUSEADDR, 1)) {

			echo 'Unable to set option on socket: '. socket_strerror(socket_last_error()) . PHP_EOL;

		}

		return TRUE;

	}


	// **************************************************************
	// *	   Connect the socket to a given server and port	*
	// **************************************************************

	function connect ($server, $port)

	{

		$result = @socket_connect ($this->socket, gethostbyname ($server), $port);

		if ($result === FALSE) {

			$this->error = "socket_connect() failed. Reason: ($result) " . socket_strerror (socket_last_error($socket)) . "\n";
                        return FALSE;
		}

		return TRUE;

	}

	// **************************************************************
	// *			   Bind the socket			*
	// **************************************************************

	function bind ($address, $port)

	{

		if (socket_bind ($this->socket, $address, $port) === false) {

			$this->error = "socket_bind() failed: reason: " . socket_strerror (socket_last_error($sock)) . "\n";
			return FALSE;

		}

		return TRUE;


	}

	// **************************************************************
	// *			Set the socket to listen		*
	// **************************************************************

	function listen ()

	{

		if (socket_listen ($this->socket, 5) === false) {

			$this->error = "socket_listen() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
			return FALSE;

		}

		return TRUE;

	}

	// **************************************************************
	// *		Set the socket to accept connections		*
	// **************************************************************

	function accept ()

	{

		if (($this->message_socket = socket_accept ($this->socket)) === false) {

			$this->error = "socket_accept() failed: reason: " . socket_strerror (socket_last_error($sock)) . "\n";
			return FALSE;

		}

		socket_getpeername ($this->message_socket, &$this->message_socket_ip);

		return TRUE;

        }

	// **************************************************************
	// *		Get the remote connection IP address		*
	// **************************************************************

	function remote_ip ()

	{

		return ($this->message_socket_ip);

	}

	// **************************************************************
	// *	    Send a message via an accepted message socket	*
	// **************************************************************

	function send_message ($string)

	{

		socket_write ($this->message_socket, $string, strlen ($string));

	}

	// **************************************************************
	// *  	   Read a message via an accepted message socket	*
	// **************************************************************

	function read_message ()

	{

		if (false === ($read_buffer = @socket_read ($this->message_socket, DNSADMIN_SOCKET_MAX_BYTES, PHP_NORMAL_READ))) {

			$this->error = "socket_read() failed: reason: " . socket_strerror (socket_last_error ($this->message_socket)) . "\n";

		}

		return $read_buffer;

	}

	// **************************************************************
	// *			Close the message socket		*
	// **************************************************************

	function close_message ()

	{

		socket_close ($this->message_socket);

	}

	// **************************************************************
	// *			Read data from the socket		*
	// **************************************************************

	function read ()

	{

		$response = @socket_read ($this->socket, DNSADMIN_SOCKET_MAX_BYTES);

		if ($response === FALSE) {

			$this->error = "socket_read() failed.\nReason: ($result) " . socket_strerror (socket_last_error($socket)) . "\n";
			return FALSE;

		}

		return $response;

	}

	// **************************************************************
	// *			Write data to the socket		*
	// **************************************************************

	function write ($string)

	{

		@socket_write ($this->socket, $string, strlen ($string));

	}

	// **************************************************************
	// *			   Close the socket			*
	// **************************************************************

	function close ()

	{

		@socket_close ($this->socket);

	}

	// --------------------------- End of dnsSocket Class ---------------------------

}

	// ------------------------- Start of dnsDaemon Class ---------------------------

class dnsDaemon {

	// Class variables

	private $socket = NULL;
	private $get_count = 0;
	private $put_count = 0;
	private $get_fails = 0;
	private $put_fails = 0;

	// **************************************************************
	// *			Construct the DNS daemon		*
	// **************************************************************

	function __construct ($ip = '0.0.0.0', $port = 25353)

	{

		// Store the PID
		
		$fh = fopen ('/tmp/dnsadmin.pid','w+');
		fwrite ($fh, getmypid());
		fclose ($fh);

		// Prepare syslog

		define_syslog_variables ();
		openlog ('dnsadmin', LOG_PID, LOG_SYSLOG);

		// Allow script to run indefinatly

		set_time_limit (0);

		// Setup the listening socket

		$this->socket = new dnsSocket ();

		$this->socket->bind ($ip, $port);

		$this->socket->listen ();

		// Log the fact the daemon was started

		syslog (LOG_INFO, "daemon started");

		// Main daemon loop

		$exit_main_loop = FALSE;

		while (!$exit_main_loop) {

			// Wait for a connection 

			$this->socket->accept ();

			// Split the recieved string into 'command:value' (eg. getzone: entorno.com)

			list ($command, $value) = split (":", trim ($this->socket->read_message ()), 2);
			$command = trim ($command);
			$value = trim ($value);

			// Check to see which command matches

			switch ($command) {

			case "shutdown":
	
				$exit_main_loop = TRUE;
				break 2;

			case "getzone":

				$this->getzone ($value);
				break;

			case "putzone":

				$this->putzone ($value);
				break;

			case "convert":

				$this->convert ($value);
				break;

			}

			// Close the message socket

			$this->socket->close_message ();

		}

		// Close the listening socket, remove the PID, and log the shutdown

		$this->socket->close ();

		@unlink ('/tmp/dnsadmin.pid');

		syslog (LOG_INFO, "daemon stopped - gets(".$this->get_count."):puts(".$this->put_count.") ");

		closelog ();

		return TRUE;

	}

	// **************************************************************
	// *		Get a given zone in XML from the server		*
	// **************************************************************	

	function getzone ($zone)

	{

		$domain = new dnsZone ();

		// If requesting all zone names on the server using * 

		if (trim ($zone) === '*') {

			$response = '<zones>'."\n";

			$domains = $domain->get_all_zones ();

			foreach ($domains as $name) $response.= ' <zone name="'.$name.'" />'."\n";

			$response.= '</zones>'."\n";

			$this->socket->send_message ($response);

			syslog (LOG_INFO, 'getzone * - '.$this->socket->remote_ip());

			return;

		}

		// Domain specified

		$domain = new dnsZone ();

		$domain->get_raw ($zone);

		$response = $domain->to_xml ();

		$this->socket->send_message ($response);

		$status = (trim ($response) === '<zone/>') ? 'UNKOWN' : 'OK' ;

		syslog (LOG_INFO, "getzone $zone - ".$status.' '.$this->socket->remote_ip());

		if ($status === 'OK') {

			$this->get_count++;

		} else {

			$this->get_fails++;

		}

	}

	// **************************************************************
	// *		Receive a zone in XML from the client		*
	// **************************************************************

	function putzone ($zone)

	{

		// Read the zone XML data from the client

		$xml = '';

		do {

			$line = $this->socket->read_message ();
			$xml .= $line;

		} while (trim ($line) !== '</zone>');

		// Create a new empty domain

		$domain = new dnsZone ();

		// Convert the XML zone to raw

		$domain->to_raw ($xml);

		// Create a domain difference array set

		$domain->set_raw ();

		// Log the action to syslog

		syslog (LOG_INFO, "putzone $zone - OK ".$this->socket->remote_ip());

		$this->put_count++;

	}

	// **************************************************************
	// *  Read in a BIND zone and respond with a dnsadmin XML file	*
	// **************************************************************

	function convert ($zonename)

	{

		// Split the zonename from the format 'zone:lines'

		list ($zonename, $zone_lines) = split (":", trim ($zonename), 2);		

		// Read the zone in, cleaning up empty lines, commented out lines and stripping spaces and tabs

		$zone = '';
		$line = '';
		$line_count = 0;

		do {

			$line = $this->socket->read_message ();
			$line_count++;
			$add = preg_replace ('/\t/', ' ', trim ($line));
			$add = preg_replace ('/\s\s+/', ' ', $add);
			if (($add != '') && (strncmp ($add,';',1) !== 0 )) $zone .= $add."\n";

                } while ($line_count < $zone_lines);

		// Now we should have the variable $zone in a clean format, ready to parse

		$domain = new dnsZone ();

		$domain->set_zone_name ($zonename);

		$zone = explode ("\n", $zone);

		// Get the TTL and SOA block

		$zone_ttl = array_shift ($zone);
		if (strncmp ('$TTL ', $zone_ttl, strlen ('$TTL ')) !== 0) return FALSE;
		$zone_ttl = substr ($zone_ttl, strlen ('$TTL '));

		// Get the SOA RNAME and MNAME
		
		$tmp = array_shift ($zone);
		$offset = stripos ($tmp, 'SOA ');
		if ($offset === FALSE) return FALSE;
		$offset += strlen ('SOA ');
		$tmp = substr ($tmp, $offset);

		$offset = stripos ($tmp, '. ');
		if ($offset === FALSE) return FALSE;
		$domain->set_soa_mname (substr ($tmp, 0, $offset));
		$end = stripos ($tmp, '. (');
		if ($end === FALSE) return FALSE;
		$end -= ($offset+2);
		$zone_rname = substr ($tmp, $offset+2, $end);

		$offset = strpos ($zone_rname, '\.');
		if ($offset !== FALSE) $zone_rname = preg_replace ( '/\\\./', '|' , $zone_rname, 1);
		$zone_rname = preg_replace ( '/\./', '@' , $zone_rname, 1);
		$domain->set_soa_rname (preg_replace ( '/\|/', '.' , $zone_rname, 1));

		array_shift ($zone);
		$domain->set_soa_refresh (array_shift ($zone));
		$domain->set_soa_retry (array_shift ($zone));
		$domain->set_soa_expire (array_shift ($zone));
		$zone_minimum = array_shift ($zone);

		// Output the zone tag

		foreach ($zone as $line) {

			$offset = stripos ($line, 'IN ');
			if ($offset === 0) $line = "$zonename $line";

			$offset = stripos ($line, 'IN ');
			$host = trim (substr ($line, 0, $offset));
			$host = trim ($host, '.');

			// Get record type

			$offset = stripos ($line, 'IN ');

			if ($offset !== FALSE) {

				$offset += strlen ('IN ');

				$record = substr ($line, $offset);

				list ($type, $data) = split (' ', $record, 2);

				$type = trim ($type);
				$data = trim ($data);

				switch ($type) {

				case 'A':

					$domain->add_a ($host, $data);
					break;

				case 'MX':

					list ($priority, $server) = split (' ', $data, 2);
					$domain->add_mx ($host, trim ($server,'.'), $priority);
					break;	

				case 'CNAME':

					$domain->add_cname ($host, trim ($data, '.'));
					break;

				case 'NS':

					$domain->add_ns ($host, trim ($data, '.'));
					break;

				}

			}			

		}

		$this->socket->send_message ($domain->to_xml());

	}

	// -------------------------- End of dnsDaemon Class ----------------------------

}

	// ---------------------- Start of dnsConversion Class --------------------------

class dnsConversion {

	// **************************************************************
	// *	     Convert Bind format zone to dnsAdmin XML		*
	// **************************************************************

	function bind_to_xml ($zone)

	{

	}

	// ------------------------ End of dnsConversion Class --------------------------

}

?>