[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 ''."\n"; // Set the zone name tag $xml = ''."\n"; // Set the SOA tag and attributes $xml.= ' '."\n"; foreach ($this->zone['hosts'] as $name=>$data) { $xml.= ' '."\n"; foreach ($data as $record=>$record_data) { switch ($record) { case "arecord": foreach ($record_data as $a_record) $xml.= ' '."\n"; break; case "cnamerecord": $xml.= ' '."\n"; break; case "nsrecord": foreach ($record_data as $ns_record) $xml.= ' '."\n"; break; case "mxrecord": foreach ($record_data as $priority=>$servers) { foreach ($servers as $server) $xml.= ' '."\n"; } break; case "txtrecord": foreach ($record_data as $txt_record) $xml.= ' '."\n"; break; case "dnsttl": $xml.= ' '."\n"; } } $xml.= ' '."\n"; } // Close the zone $xml.= ''."\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 = ''."\n"; $domains = $domain->get_all_zones (); foreach ($domains as $name) $response.= ' '."\n"; $response.= ''."\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) === '') ? '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) !== ''); // 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 -------------------------- } ?>