inc/dns.inc.php
author rejo
Mon, 23 Jul 2007 22:05:19 +0000
changeset 38 cf767482333a
parent 16 79b09e1e2985
child 47 ae140472d97c
permissions -rwxr-xr-x
[feladat @ 85] The type of zone wasn't show to users with access level 1. If a user with access level 1 did have access to a slave zone the user did not see the IP of the master nameserver of that zone. Now the user will the IP address (readonly and only if one is set). Bugfix. If no master IP for a slave zone is given, a warning is shown regardless of the userlevel. Bugfix. Both the "add record" and "edit record" buttons in the "edit zone" screen is no longer available for users with level 1 for domains of type "slave". Bug report by Antonio Prado. Some PHP and HTML cleanup (removing of empty tags and unnecessary repeatings of calls to a single function).

<?

/*
 * Validates an IPv4 IP.
 * returns true if valid.
 */
function validate_input($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->queryOne("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->queryOne("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.	*
		 *					*
		 ***************************************/
?>