1
|
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 |
?> |