inc/dns.inc.php
changeset 1 58094faf794d
child 6 9fcac40c1b0e
equal deleted inserted replaced
0:2cd8c1649ba9 1:58094faf794d
       
     1 <?
       
     2 
       
     3 // +--------------------------------------------------------------------+
       
     4 // | PowerAdmin                                                         |
       
     5 // +--------------------------------------------------------------------+
       
     6 // | Copyright (c) 1997-2002 The PowerAdmin Team                        |
       
     7 // +--------------------------------------------------------------------+
       
     8 // | This source file is subject to the license carried by the overal   |
       
     9 // | program PowerAdmin as found on http://poweradmin.sf.net            |
       
    10 // | The PowerAdmin program falls under the QPL License:                |
       
    11 // | http://www.trolltech.com/developer/licensing/qpl.html              |
       
    12 // +--------------------------------------------------------------------+
       
    13 // | Authors: Roeland Nieuwenhuis <trancer <AT> trancer <DOT> nl>       |
       
    14 // |          Sjeemz <sjeemz <AT> sjeemz <DOT> nl>                      |
       
    15 // +--------------------------------------------------------------------+
       
    16 
       
    17 // Filename: dns.inc.php
       
    18 // Startdate: 26-10-2002
       
    19 // Description: checks whether a given content is valid.
       
    20 // A lot of DNS Record features are found to be here and also are to be placed here.
       
    21 // If they are authorized this code handles that they can access stuff.
       
    22 //
       
    23 // $Id: dns.inc.php,v 1.23 2003/02/23 21:31:13 azurazu Exp $
       
    24 //
       
    25 
       
    26 
       
    27 /*
       
    28  * Validates an IPv4 IP.
       
    29  * returns true if valid.
       
    30  */
       
    31 function validate_input($recordid, $zoneid, $type, &$content, &$name, &$prio, &$ttl)
       
    32 {
       
    33 	global $db;
       
    34 
       
    35 	// Has to validate content first then it can do the rest
       
    36 	// Since if content is invalid already it can aswell be just removed
       
    37 	// Check first if content is IPv4, IPv6 or Hostname
       
    38 	// We accomplish this by just running all tests over it
       
    39 	// We start with IPv6 since its not able to have these ip's in domains.
       
    40 	//
       
    41 	// <TODO>
       
    42 	// The nocheck has to move to the configuration file
       
    43 	// </TODO>
       
    44 	//
       
    45 	$domain = get_domain_name_from_id($zoneid);
       
    46 	$nocheck = array('SOA', 'HINFO', 'NAPTR', 'URL', 'MBOXFW', 'TXT');
       
    47 	$hostname = false;
       
    48 	$ip4 = false;
       
    49 	$ip6 = false;
       
    50 
       
    51 	if(!in_array(strtoupper($type), $nocheck))
       
    52 	{
       
    53 
       
    54 		if(!is_valid_ip6($content))
       
    55 		{
       
    56 			if(!is_valid_ip($content))
       
    57 			{
       
    58 				if(!is_valid_hostname($content))
       
    59 				{
       
    60 					error(ERR_DNS_CONTENT);
       
    61 				}
       
    62 				else
       
    63 				{
       
    64 					$hostname = true;
       
    65 				}
       
    66 			}
       
    67 			else
       
    68 			{
       
    69 				$ip4 = true;
       
    70 			}
       
    71 		}
       
    72 		else
       
    73 		{
       
    74 			$ip6 = true;
       
    75 		}
       
    76 	}
       
    77 
       
    78 	// Prepare total hostname.
       
    79 
       
    80 	if($name == '*')
       
    81 	{
       
    82 		$wildcard = true;
       
    83 	}
       
    84 
       
    85 	if ($name=="0") {
       
    86 	   $name=$name.".".$domain;
       
    87 	} else {
       
    88 	   $name = ($name) ? $name.".".$domain : $domain;
       
    89 	}
       
    90 
       
    91 	if (preg_match('!@\.!i', $name))
       
    92 	{
       
    93 		$name = str_replace('@.', '@', $name);
       
    94 	}
       
    95 
       
    96 	if(!$wildcard)
       
    97 	{
       
    98 		if(!is_valid_hostname($name))
       
    99 		{
       
   100 			error(ERR_DNS_HOSTNAME);
       
   101 		}
       
   102 	}
       
   103 
       
   104 	// Check record type (if it exists in our allowed list.
       
   105 	if (!in_array(strtoupper($type), get_record_types()))
       
   106 	{
       
   107 		error(ERR_DNS_RECORDTYPE);
       
   108 	}
       
   109 
       
   110 	// Start handling the demands for the functions.
       
   111 	// Validation for IN A records. Can only have an IP. Nothing else.
       
   112 	if ($type == 'A' && !$ip4)
       
   113 	{
       
   114 		error(ERR_DNS_IPV4);
       
   115 	}
       
   116 
       
   117 	if ($type == 'AAAA' && !$ip6)
       
   118 	{
       
   119 		error(ERR_DNS_IPV6);
       
   120 	}
       
   121 
       
   122 	if ($type == 'CNAME' && $hostname)
       
   123 	{
       
   124 		if(!is_valid_cname($name))
       
   125 		{
       
   126 			error(ERR_DNS_CNAME);
       
   127 		}
       
   128 	}
       
   129 
       
   130 	if ($type == 'NS')
       
   131 	{
       
   132 		$status = is_valid_ns($content, $hostname);
       
   133 		if($status == -1)
       
   134 		{
       
   135 			error(ERR_DNS_NS_HNAME);
       
   136 		}
       
   137 		elseif($status == -2)
       
   138 		{
       
   139 			error(ERR_DNS_NS_CNAME);
       
   140 		}
       
   141 		// Otherwise its ok
       
   142 	}
       
   143 
       
   144 	if ($type == 'SOA')
       
   145 	{
       
   146 		$status = is_valid_soa($content, $zoneid);
       
   147 		if($status == -1)
       
   148 		{
       
   149 			error(ERR_DNS_SOA_UNIQUE);
       
   150 			// Make nicer error
       
   151 		}
       
   152 		elseif($status == -2)
       
   153 		{
       
   154 			error(ERR_DNS_SOA_NUMERIC);
       
   155 		}
       
   156 	}
       
   157 
       
   158 	// HINFO and TXT require no validation.
       
   159 
       
   160 	if ($type == 'URL')
       
   161 	{
       
   162 		if(!is_valid_url($content))
       
   163 		{
       
   164 			error(ERR_INV_URL);
       
   165 		}
       
   166 	}
       
   167 	if ($type == 'MBOXFW')
       
   168 	{
       
   169 		if(!is_valid_mboxfw($content))
       
   170 		{
       
   171 			error(ERR_INV_EMAIL);
       
   172 		}
       
   173 	}
       
   174 
       
   175 	// NAPTR has to be done.
       
   176 	// Do we want that?
       
   177 	// http://www.ietf.org/rfc/rfc2915.txt
       
   178 	// http://www.zvon.org/tmRFC/RFC2915/Output/chapter2.html
       
   179 	// http://www.zvon.org/tmRFC/RFC3403/Output/chapter4.html
       
   180 
       
   181 	// See if the prio field is valid and if we have one.
       
   182 	// If we dont have one and the type is MX record, give it value '10'
       
   183 	if($type == 'NAPTR')
       
   184 	{
       
   185 
       
   186 	}
       
   187 	
       
   188 	if($type == 'MX')
       
   189 	{
       
   190 		if($hostname)
       
   191 		{
       
   192 			$status = is_valid_mx($content, $prio);
       
   193 			if($status == -1)
       
   194 			{
       
   195 				error(ERR_DNS_MX_CNAME);
       
   196 			}
       
   197 			elseif($status == -2)
       
   198 			{
       
   199 				error(ERR_DNS_MX_PRIO);
       
   200 			}
       
   201 		}
       
   202 		else
       
   203 		{
       
   204 			error("If you specify an MX record it must be a hostname");
       
   205 		}
       
   206 	}
       
   207 	else
       
   208 	{
       
   209 		$prio='';
       
   210 	}
       
   211 	// Validate the TTL, it has to be numeric.
       
   212 	$ttl = (!isset($ttl) || !is_numeric($ttl)) ? $DEFAULT_TTL : $ttl;
       
   213 }
       
   214 
       
   215 
       
   216 
       
   217 		/****************************************
       
   218 		 *					*
       
   219 		 * RECORD VALIDATING PART.		*
       
   220 		 * CHANGES HERE SHOULD BE CONSIDERED	*
       
   221 		 * THEY REQUIRE KNOWLEDGE ABOUT THE 	*
       
   222 		 * DNS SPECIFICATIONS			*
       
   223 		 *					*
       
   224 		 ***************************************/
       
   225 
       
   226 
       
   227 /*
       
   228  * Validatis a CNAME record by the name it will have and its destination
       
   229  *
       
   230  */
       
   231 function is_valid_cname($dest)
       
   232 {
       
   233 	/*
       
   234 	 * This is really EVIL.
       
   235 	 * If the new record (a CNAME) record is being pointed to by a MX record or NS record we have to bork.
       
   236 	 * this is the idea.
       
   237 	 *
       
   238 	 * MX record: blaat.nl MX mail.blaat.nl
       
   239 	 * Now we look what mail.blaat.nl is
       
   240 	 * We discover the following:
       
   241 	 * mail.blaat.nl CNAME bork.blaat.nl
       
   242 	 * This is NOT allowed! mail.onthanet.nl can not be a CNAME!
       
   243 	 * The same goes for NS. mail.blaat.nl must have a normal IN A record.
       
   244 	 * It MAY point to a CNAME record but its not wished. Lets not support it.
       
   245 	 */
       
   246 
       
   247 	global $db;
       
   248 
       
   249 	// Check if there are other records with this information of the following types.
       
   250 	// P.S. we might add CNAME to block CNAME recursion and chains.
       
   251 	$blockedtypes = " AND (type='MX' OR type='NS')";
       
   252 
       
   253 	$cnamec = "SELECT type, content FROM records WHERE content='$dest'" . $blockedtypes;
       
   254 	$result = $db->query($cnamec);
       
   255 
       
   256 	if($result->numRows() > 0)
       
   257 	{
       
   258 		return false;
       
   259 		// Lets inform the user he is doing something EVIL.
       
   260 		// Ok we found a record that has our content field in their content field.
       
   261 	}
       
   262 	return true;
       
   263 }
       
   264 
       
   265 
       
   266 /*
       
   267  * Checks if something is a valid domain.
       
   268  * Checks for domainname with the allowed characters <a,b,...z,A,B,...Z> and - and _.
       
   269  * This part must be followed by a 2 to 4 character TLD.
       
   270  */
       
   271 function is_valid_domain($domain)
       
   272 {
       
   273 	if ((eregi("^[0-9a-z]([-.]?[0-9a-z])*\\.[a-z]{2,4}$", $domain)) && (strlen($domain) <= 128))
       
   274 	{
       
   275 		return true;
       
   276 	}
       
   277 	return false;
       
   278 }
       
   279 
       
   280 
       
   281 /*
       
   282  * Validates if given hostname is allowed.
       
   283  * returns true if allowed.
       
   284  */
       
   285 function is_valid_hostname($host)
       
   286 {
       
   287 	if(count(explode(".", $host)) == 1)
       
   288 	{
       
   289 		return false;
       
   290 	}
       
   291 
       
   292 	// Its not perfect (in_addr.int is allowed) but works for now.
       
   293 
       
   294 	if(preg_match('!(ip6|in-addr).(arpa|int)$!i', $host))
       
   295 	{
       
   296 		if(preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host))
       
   297 		{
       
   298 			return true;
       
   299 		}
       
   300 		return false;
       
   301 	}
       
   302 
       
   303 	// Validate further.
       
   304 	return (preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host)) ? true : false;
       
   305 }
       
   306 
       
   307 
       
   308 /*
       
   309  * Validates an IPv4 IP.
       
   310  * returns true if valid.
       
   311  */
       
   312 function is_valid_ip($ip)
       
   313 {
       
   314 	// Stop reading at this point. Scroll down to the next function...
       
   315 	// Ok... you didn't stop reading... now you have to rewrite the whole function! enjoy ;-)
       
   316 	// Trance unborked it. Twice even!
       
   317 	return ($ip == long2ip(ip2long($ip))) ? true : false;
       
   318 
       
   319 }
       
   320 
       
   321 
       
   322 /*
       
   323  * Validates an IPv6 IP.
       
   324  * returns true if valid.
       
   325  */
       
   326 function is_valid_ip6($ip)
       
   327 {
       
   328 	// Validates if the given IP is truly an IPv6 address.
       
   329 	// Precondition: have a string
       
   330 	// Postcondition: false: Error in IP
       
   331 	//                true: IP is correct
       
   332 	// Requires: String
       
   333 	// Date: 10-sep-2002
       
   334 	if(preg_match('!^[A-F0-9:]{1,39}$!i', $ip) == true)
       
   335 	{
       
   336 		// Not 3 ":" or more.
       
   337 		$p = explode(':::', $ip);
       
   338 		if(sizeof($p) > 1)
       
   339 		{
       
   340 			return false;
       
   341 		}
       
   342 		// Find if there is only one occurence of "::".
       
   343 		$p = explode('::', $ip);
       
   344 		if(sizeof($p) > 2)
       
   345 		{
       
   346 			return false;
       
   347 		}
       
   348 		// Not more than 8 octects
       
   349 		$p = explode(':', $ip);
       
   350 
       
   351 		if(sizeof($p) > 8)
       
   352 		{
       
   353 			return false;
       
   354 		}
       
   355 
       
   356 		// Check octet length
       
   357 		foreach($p as $checkPart)
       
   358 		{
       
   359 			if(strlen($checkPart) > 4)
       
   360 			{
       
   361 				return false;
       
   362 			}
       
   363 		}
       
   364 		return true;
       
   365 	}
       
   366 	return false;
       
   367 }
       
   368 
       
   369 
       
   370 /*
       
   371  * FANCY RECORD.
       
   372  * Validates if the fancy record mboxfw is an actual email address.
       
   373  */
       
   374 function is_valid_mboxfw($email)
       
   375 {
       
   376 	return is_valid_email($email);
       
   377 }
       
   378 
       
   379 
       
   380 /*
       
   381  * Validates MX records.
       
   382  * an MX record cant point to a CNAME record. This has to be checked.
       
   383  * this function also sets a proper priority.
       
   384  */
       
   385 function is_valid_mx($content, &$prio)
       
   386 {
       
   387 	global $db;
       
   388 	// See if the destination to which this MX is pointing is NOT a CNAME record.
       
   389 	// Check inside our dns server.
       
   390 	if($db->getOne("SELECT count(id) FROM records WHERE name='$content' AND type='CNAME'") > 0)
       
   391 	{
       
   392 		return -1;
       
   393 	}
       
   394 	else
       
   395 	{
       
   396 		// Fix the proper priority for the record.
       
   397 		// Bugfix, thanks Oscar :)
       
   398 		if(!isset($prio))
       
   399 		{
       
   400 			$prio = 10;
       
   401 		}
       
   402 		if(!is_numeric($prio))
       
   403 		{
       
   404 			if($prio == '')
       
   405 			{
       
   406 				$prio = 10;
       
   407 			}
       
   408 			else
       
   409 			{
       
   410 				return -2;
       
   411 			}
       
   412 		}
       
   413 	}
       
   414 	return 1;
       
   415 }
       
   416 
       
   417 /*
       
   418  * Validates NS records.
       
   419  * an NS record cant point to a CNAME record. This has to be checked.
       
   420  * $hostname directive means if its a hostname or not (this to avoid that NS records get ip fields)
       
   421  * NS must have a hostname, it is not allowed to have an IP.
       
   422  */
       
   423 function is_valid_ns($content, $hostname)
       
   424 {
       
   425 	global $db;
       
   426 	// Check if the field is a hostname, it MUST be a hostname.
       
   427 	if(!$hostname)
       
   428 	{
       
   429 		return -1;
       
   430 		// "an IN NS field must be a hostname."
       
   431 	}
       
   432 
       
   433 	if($db->getOne("SELECT count(id) FROM records WHERE name='$content' AND type='CNAME'") > 0)
       
   434 	{
       
   435 		return -2;
       
   436 		// "You can not point a NS record to a CNAME record. Remove/rename the CNAME record first or take another name."
       
   437 
       
   438 	}
       
   439 	return 1;
       
   440 }
       
   441 
       
   442 
       
   443 /*
       
   444  * Function to check the validity of SOA records.
       
   445  * return values: true if succesful
       
   446  */
       
   447 function is_valid_soa(&$content, $zoneid)
       
   448 {
       
   449 
       
   450 	/*
       
   451 	 * SOA (start of authority)
       
   452 	 * there is only _ONE_ SOA record allowed in every zone.
       
   453 	 * Validate SOA record
       
   454 	 * The Start of Authority record is one of the most complex available. It specifies a lot
       
   455 	 * about a domain: the name of the master nameserver ('the primary'), the hostmaster and
       
   456 	 * a set of numbers indicating how the data in this domain expires and how often it needs
       
   457 	 * to be checked. Further more, it contains a serial number which should rise on each change
       
   458 	 * of the domain.
       
   459 	 					    2002120902 28800 7200 604800 10800
       
   460 	 * The stored format is: primary hostmaster serial refresh retry expire default_ttl
       
   461 	 * From the powerdns documentation.
       
   462 	 */
       
   463 
       
   464 
       
   465 	// Check if there already is an occurence of a SOA, if so see if its not the one we are currently changing
       
   466 	$return = get_records_by_type_from_domid("SOA", $zoneid);
       
   467 	if($return->numRows() > 1)
       
   468 	{
       
   469 		return -1;
       
   470 	}
       
   471 
       
   472 
       
   473 	$soacontent = explode(" ", $content);
       
   474 	// Field is at least one otherwise it wouldnt even get here.
       
   475 	if(is_valid_hostname($soacontent[0]))
       
   476 	{
       
   477 		$totalsoa = $soacontent[0];
       
   478 		// It doesnt matter what field 2 contains, but lets check if its there
       
   479 		// We assume the 2nd field wont have numbers, otherwise its a TTL field
       
   480 		if(count($soacontent) > 1)
       
   481 		{
       
   482 			if(is_numeric($soacontent[1]))
       
   483 			{
       
   484 				// its a TTL field, or at least not hostmaster or alike
       
   485 				// Set final string to the default hostmaster addy
       
   486 				global $HOSTMASTER;
       
   487 				$totalsoa .= " ". $HOSTMASTER;
       
   488 			}
       
   489 			else
       
   490 			{
       
   491 				$totalsoa .= " ".$soacontent[1];
       
   492 			}
       
   493 			// For loop to iterate over the numbers
       
   494 			$imax = count($soacontent);
       
   495 			for($i = 2; ($i < $imax) && ($i < 7); $i++)
       
   496 			{
       
   497 				if(!is_numeric($soacontent[$i]))
       
   498 				{
       
   499 					return -2;
       
   500 				}
       
   501 				else
       
   502 				{
       
   503 					$totalsoa .= " ".$soacontent[$i];
       
   504 				}
       
   505 			}
       
   506 			if($i > 7)
       
   507 			{
       
   508 				error(ERR_DNS_SOA_NUMERIC_FIELDS);
       
   509 			}
       
   510 		}
       
   511 	}
       
   512 	else
       
   513 	{
       
   514 		error(ERR_DNS_SOA_HOSTNAME);
       
   515 	}
       
   516 	$content = $totalsoa;
       
   517 	return 1;
       
   518 }
       
   519 
       
   520 
       
   521 function is_valid_url($url)
       
   522 {
       
   523 	return preg_match('!^(http://)(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+([//]([0-9a-z//~#%&\'_\-+=:?.]*))?$!i',  $url);
       
   524 }
       
   525 
       
   526 		/****************************************
       
   527 		 *					*
       
   528 		 *    END OF RECORD VALIDATING PART.	*
       
   529 		 *					*
       
   530 		 ***************************************/
       
   531 ?>