<?
// 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 --------------------------
}
?>