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