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