inc/dns.inc.php
changeset 1 58094faf794d
child 6 9fcac40c1b0e
--- /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 @@
+<?
+
+// +--------------------------------------------------------------------+
+// | PowerAdmin                                                         |
+// +--------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PowerAdmin Team                        |
+// +--------------------------------------------------------------------+
+// | This source file is subject to the license carried by the overal   |
+// | program PowerAdmin as found on http://poweradmin.sf.net            |
+// | The PowerAdmin program falls under the QPL License:                |
+// | http://www.trolltech.com/developer/licensing/qpl.html              |
+// +--------------------------------------------------------------------+
+// | Authors: Roeland Nieuwenhuis <trancer <AT> trancer <DOT> nl>       |
+// |          Sjeemz <sjeemz <AT> sjeemz <DOT> 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.
+	//
+	// <TODO>
+	// The nocheck has to move to the configuration file
+	// </TODO>
+	//
+	$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 <a,b,...z,A,B,...Z> 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.	*
+		 *					*
+		 ***************************************/
+?>