diff -r 2cd8c1649ba9 -r 58094faf794d inc/dns.inc.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/dns.inc.php Tue Apr 10 22:40:43 2007 +0000 @@ -0,0 +1,531 @@ + trancer nl> | +// | Sjeemz sjeemz nl> | +// +--------------------------------------------------------------------+ + +// Filename: dns.inc.php +// Startdate: 26-10-2002 +// Description: checks whether a given content is valid. +// A lot of DNS Record features are found to be here and also are to be placed here. +// If they are authorized this code handles that they can access stuff. +// +// $Id: dns.inc.php,v 1.23 2003/02/23 21:31:13 azurazu Exp $ +// + + +/* + * Validates an IPv4 IP. + * returns true if valid. + */ +function validate_input($recordid, $zoneid, $type, &$content, &$name, &$prio, &$ttl) +{ + global $db; + + // Has to validate content first then it can do the rest + // Since if content is invalid already it can aswell be just removed + // Check first if content is IPv4, IPv6 or Hostname + // We accomplish this by just running all tests over it + // We start with IPv6 since its not able to have these ip's in domains. + // + // + // The nocheck has to move to the configuration file + // + // + $domain = get_domain_name_from_id($zoneid); + $nocheck = array('SOA', 'HINFO', 'NAPTR', 'URL', 'MBOXFW', 'TXT'); + $hostname = false; + $ip4 = false; + $ip6 = false; + + if(!in_array(strtoupper($type), $nocheck)) + { + + if(!is_valid_ip6($content)) + { + if(!is_valid_ip($content)) + { + if(!is_valid_hostname($content)) + { + error(ERR_DNS_CONTENT); + } + else + { + $hostname = true; + } + } + else + { + $ip4 = true; + } + } + else + { + $ip6 = true; + } + } + + // Prepare total hostname. + + if($name == '*') + { + $wildcard = true; + } + + if ($name=="0") { + $name=$name.".".$domain; + } else { + $name = ($name) ? $name.".".$domain : $domain; + } + + if (preg_match('!@\.!i', $name)) + { + $name = str_replace('@.', '@', $name); + } + + if(!$wildcard) + { + if(!is_valid_hostname($name)) + { + error(ERR_DNS_HOSTNAME); + } + } + + // Check record type (if it exists in our allowed list. + if (!in_array(strtoupper($type), get_record_types())) + { + error(ERR_DNS_RECORDTYPE); + } + + // Start handling the demands for the functions. + // Validation for IN A records. Can only have an IP. Nothing else. + if ($type == 'A' && !$ip4) + { + error(ERR_DNS_IPV4); + } + + if ($type == 'AAAA' && !$ip6) + { + error(ERR_DNS_IPV6); + } + + if ($type == 'CNAME' && $hostname) + { + if(!is_valid_cname($name)) + { + error(ERR_DNS_CNAME); + } + } + + if ($type == 'NS') + { + $status = is_valid_ns($content, $hostname); + if($status == -1) + { + error(ERR_DNS_NS_HNAME); + } + elseif($status == -2) + { + error(ERR_DNS_NS_CNAME); + } + // Otherwise its ok + } + + if ($type == 'SOA') + { + $status = is_valid_soa($content, $zoneid); + if($status == -1) + { + error(ERR_DNS_SOA_UNIQUE); + // Make nicer error + } + elseif($status == -2) + { + error(ERR_DNS_SOA_NUMERIC); + } + } + + // HINFO and TXT require no validation. + + if ($type == 'URL') + { + if(!is_valid_url($content)) + { + error(ERR_INV_URL); + } + } + if ($type == 'MBOXFW') + { + if(!is_valid_mboxfw($content)) + { + error(ERR_INV_EMAIL); + } + } + + // NAPTR has to be done. + // Do we want that? + // http://www.ietf.org/rfc/rfc2915.txt + // http://www.zvon.org/tmRFC/RFC2915/Output/chapter2.html + // http://www.zvon.org/tmRFC/RFC3403/Output/chapter4.html + + // See if the prio field is valid and if we have one. + // If we dont have one and the type is MX record, give it value '10' + if($type == 'NAPTR') + { + + } + + if($type == 'MX') + { + if($hostname) + { + $status = is_valid_mx($content, $prio); + if($status == -1) + { + error(ERR_DNS_MX_CNAME); + } + elseif($status == -2) + { + error(ERR_DNS_MX_PRIO); + } + } + else + { + error("If you specify an MX record it must be a hostname"); + } + } + else + { + $prio=''; + } + // Validate the TTL, it has to be numeric. + $ttl = (!isset($ttl) || !is_numeric($ttl)) ? $DEFAULT_TTL : $ttl; +} + + + + /**************************************** + * * + * RECORD VALIDATING PART. * + * CHANGES HERE SHOULD BE CONSIDERED * + * THEY REQUIRE KNOWLEDGE ABOUT THE * + * DNS SPECIFICATIONS * + * * + ***************************************/ + + +/* + * Validatis a CNAME record by the name it will have and its destination + * + */ +function is_valid_cname($dest) +{ + /* + * This is really EVIL. + * If the new record (a CNAME) record is being pointed to by a MX record or NS record we have to bork. + * this is the idea. + * + * MX record: blaat.nl MX mail.blaat.nl + * Now we look what mail.blaat.nl is + * We discover the following: + * mail.blaat.nl CNAME bork.blaat.nl + * This is NOT allowed! mail.onthanet.nl can not be a CNAME! + * The same goes for NS. mail.blaat.nl must have a normal IN A record. + * It MAY point to a CNAME record but its not wished. Lets not support it. + */ + + global $db; + + // Check if there are other records with this information of the following types. + // P.S. we might add CNAME to block CNAME recursion and chains. + $blockedtypes = " AND (type='MX' OR type='NS')"; + + $cnamec = "SELECT type, content FROM records WHERE content='$dest'" . $blockedtypes; + $result = $db->query($cnamec); + + if($result->numRows() > 0) + { + return false; + // Lets inform the user he is doing something EVIL. + // Ok we found a record that has our content field in their content field. + } + return true; +} + + +/* + * Checks if something is a valid domain. + * Checks for domainname with the allowed characters and - and _. + * This part must be followed by a 2 to 4 character TLD. + */ +function is_valid_domain($domain) +{ + if ((eregi("^[0-9a-z]([-.]?[0-9a-z])*\\.[a-z]{2,4}$", $domain)) && (strlen($domain) <= 128)) + { + return true; + } + return false; +} + + +/* + * Validates if given hostname is allowed. + * returns true if allowed. + */ +function is_valid_hostname($host) +{ + if(count(explode(".", $host)) == 1) + { + return false; + } + + // Its not perfect (in_addr.int is allowed) but works for now. + + if(preg_match('!(ip6|in-addr).(arpa|int)$!i', $host)) + { + if(preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host)) + { + return true; + } + return false; + } + + // Validate further. + return (preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host)) ? true : false; +} + + +/* + * Validates an IPv4 IP. + * returns true if valid. + */ +function is_valid_ip($ip) +{ + // Stop reading at this point. Scroll down to the next function... + // Ok... you didn't stop reading... now you have to rewrite the whole function! enjoy ;-) + // Trance unborked it. Twice even! + return ($ip == long2ip(ip2long($ip))) ? true : false; + +} + + +/* + * Validates an IPv6 IP. + * returns true if valid. + */ +function is_valid_ip6($ip) +{ + // Validates if the given IP is truly an IPv6 address. + // Precondition: have a string + // Postcondition: false: Error in IP + // true: IP is correct + // Requires: String + // Date: 10-sep-2002 + if(preg_match('!^[A-F0-9:]{1,39}$!i', $ip) == true) + { + // Not 3 ":" or more. + $p = explode(':::', $ip); + if(sizeof($p) > 1) + { + return false; + } + // Find if there is only one occurence of "::". + $p = explode('::', $ip); + if(sizeof($p) > 2) + { + return false; + } + // Not more than 8 octects + $p = explode(':', $ip); + + if(sizeof($p) > 8) + { + return false; + } + + // Check octet length + foreach($p as $checkPart) + { + if(strlen($checkPart) > 4) + { + return false; + } + } + return true; + } + return false; +} + + +/* + * FANCY RECORD. + * Validates if the fancy record mboxfw is an actual email address. + */ +function is_valid_mboxfw($email) +{ + return is_valid_email($email); +} + + +/* + * Validates MX records. + * an MX record cant point to a CNAME record. This has to be checked. + * this function also sets a proper priority. + */ +function is_valid_mx($content, &$prio) +{ + global $db; + // See if the destination to which this MX is pointing is NOT a CNAME record. + // Check inside our dns server. + if($db->getOne("SELECT count(id) FROM records WHERE name='$content' AND type='CNAME'") > 0) + { + return -1; + } + else + { + // Fix the proper priority for the record. + // Bugfix, thanks Oscar :) + if(!isset($prio)) + { + $prio = 10; + } + if(!is_numeric($prio)) + { + if($prio == '') + { + $prio = 10; + } + else + { + return -2; + } + } + } + return 1; +} + +/* + * Validates NS records. + * an NS record cant point to a CNAME record. This has to be checked. + * $hostname directive means if its a hostname or not (this to avoid that NS records get ip fields) + * NS must have a hostname, it is not allowed to have an IP. + */ +function is_valid_ns($content, $hostname) +{ + global $db; + // Check if the field is a hostname, it MUST be a hostname. + if(!$hostname) + { + return -1; + // "an IN NS field must be a hostname." + } + + if($db->getOne("SELECT count(id) FROM records WHERE name='$content' AND type='CNAME'") > 0) + { + return -2; + // "You can not point a NS record to a CNAME record. Remove/rename the CNAME record first or take another name." + + } + return 1; +} + + +/* + * Function to check the validity of SOA records. + * return values: true if succesful + */ +function is_valid_soa(&$content, $zoneid) +{ + + /* + * SOA (start of authority) + * there is only _ONE_ SOA record allowed in every zone. + * Validate SOA record + * The Start of Authority record is one of the most complex available. It specifies a lot + * about a domain: the name of the master nameserver ('the primary'), the hostmaster and + * a set of numbers indicating how the data in this domain expires and how often it needs + * to be checked. Further more, it contains a serial number which should rise on each change + * of the domain. + 2002120902 28800 7200 604800 10800 + * The stored format is: primary hostmaster serial refresh retry expire default_ttl + * From the powerdns documentation. + */ + + + // Check if there already is an occurence of a SOA, if so see if its not the one we are currently changing + $return = get_records_by_type_from_domid("SOA", $zoneid); + if($return->numRows() > 1) + { + return -1; + } + + + $soacontent = explode(" ", $content); + // Field is at least one otherwise it wouldnt even get here. + if(is_valid_hostname($soacontent[0])) + { + $totalsoa = $soacontent[0]; + // It doesnt matter what field 2 contains, but lets check if its there + // We assume the 2nd field wont have numbers, otherwise its a TTL field + if(count($soacontent) > 1) + { + if(is_numeric($soacontent[1])) + { + // its a TTL field, or at least not hostmaster or alike + // Set final string to the default hostmaster addy + global $HOSTMASTER; + $totalsoa .= " ". $HOSTMASTER; + } + else + { + $totalsoa .= " ".$soacontent[1]; + } + // For loop to iterate over the numbers + $imax = count($soacontent); + for($i = 2; ($i < $imax) && ($i < 7); $i++) + { + if(!is_numeric($soacontent[$i])) + { + return -2; + } + else + { + $totalsoa .= " ".$soacontent[$i]; + } + } + if($i > 7) + { + error(ERR_DNS_SOA_NUMERIC_FIELDS); + } + } + } + else + { + error(ERR_DNS_SOA_HOSTNAME); + } + $content = $totalsoa; + return 1; +} + + +function is_valid_url($url) +{ + return preg_match('!^(http://)(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+([//]([0-9a-z//~#%&\'_\-+=:?.]*))?$!i', $url); +} + + /**************************************** + * * + * END OF RECORD VALIDATING PART. * + * * + ***************************************/ +?>