inc/dns.inc.php
author rejo
Tue, 10 Jul 2007 21:24:06 +0000
changeset 37 b785e54690ce
parent 16 79b09e1e2985
child 47 ae140472d97c
permissions -rwxr-xr-x
[feladat @ 84] Bugfix. The function zone_count() now also counts zones an owner has only partial access to, not just those zones the owner has full access to. This fixes just the count, the zones a user has partial access to are not (yet!) shown in the "list zones" page. Bugfix. In the zone listing the "edit" button is now show for users with access level 1. Untill now they were presented an overview of the zones they could change, but there was no link for them to actually edit the zone. Bugfix. Some of the buttons in the "edit zone" interface that are of no use to a user with access level 1 have been hidden. Bugfix. Make sure a user with access level 1 with only partial access to a zone cannot add new records to that zone. Only the zone owner should be able to add new record. Bugfix. If a user with access level 1 edits a record in a zone he has only partial access to, an error was shown because of call to a non- existing function in the PEAR:MDB2. This bug was most likely introduced while migrating from PEAR:DB to PEAR:MDB2. Bugfix. A user with access level 1 was able to delete all records of a zone he has only partial access to. Some additional checks have been added. Bugfix. If a user with accees level 1 has partial access to one or more zones starting with a certain character, but did not own at least one entire zone starting with the same character, the character wasn't clickable in the "list zone" page. Interface. If no record or zone id is given for delete_record.php or delete_domain.php, don't just die but echo a nice message. The i18n files have not yet been updated to reflect this change. Interface. If no master IP is given in delete_supermaster.php, don't just die but echo a nice message. The i18n files have not yet been updated to reflect this change. [All fixes by Peter Beernink.]

<?

/*
 * 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.	*
		 *					*
		 ***************************************/
?>