71
|
1 |
<?php |
1
|
2 |
|
119
|
3 |
/* Poweradmin, a friendly web-based admin tool for PowerDNS. |
47
|
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 |
|
138
|
22 |
function validate_input($zid, $type, &$content, &$name, &$prio, &$ttl) |
1
|
23 |
{ |
|
24 |
global $db; |
138
|
25 |
$domain = get_domain_name_from_id($zid); |
1
|
26 |
$nocheck = array('SOA', 'HINFO', 'NAPTR', 'URL', 'MBOXFW', 'TXT'); |
|
27 |
$hostname = false; |
|
28 |
$ip4 = false; |
|
29 |
$ip6 = false; |
|
30 |
|
82
|
31 |
if(!in_array(strtoupper($type), $nocheck)) { |
|
32 |
if(!is_valid_ip6($content)) { |
|
33 |
if(!is_valid_ip($content)) { |
|
34 |
if(!is_valid_hostname($content)) { |
1
|
35 |
error(ERR_DNS_CONTENT); |
82
|
36 |
return false; |
|
37 |
} else { |
1
|
38 |
$hostname = true; |
|
39 |
} |
82
|
40 |
} else { |
1
|
41 |
$ip4 = true; |
|
42 |
} |
82
|
43 |
} else { |
1
|
44 |
$ip6 = true; |
|
45 |
} |
|
46 |
} |
|
47 |
|
|
48 |
// Prepare total hostname. |
82
|
49 |
if ($name == '*') { |
1
|
50 |
$wildcard = true; |
79
|
51 |
} else { |
|
52 |
$wildcard = false; |
1
|
53 |
} |
|
54 |
|
97
|
55 |
if (preg_match("/@/", $name)) { |
|
56 |
$name = $domain ; |
|
57 |
} elseif ( !(preg_match("/$domain$/i", $name))) { |
|
58 |
|
|
59 |
if ( isset($name) && $name != "" ) { |
|
60 |
$name = $name . "." . $domain ; |
|
61 |
} else { |
|
62 |
$name = $domain ; |
|
63 |
} |
96
|
64 |
} |
|
65 |
|
82
|
66 |
if(!$wildcard) { |
97
|
67 |
if(!is_valid_hostname($name)) { |
1
|
68 |
error(ERR_DNS_HOSTNAME); |
82
|
69 |
return false; |
1
|
70 |
} |
|
71 |
} |
|
72 |
|
|
73 |
// Check record type (if it exists in our allowed list. |
82
|
74 |
if (!in_array(strtoupper($type), get_record_types())) { |
1
|
75 |
error(ERR_DNS_RECORDTYPE); |
82
|
76 |
return false; |
1
|
77 |
} |
|
78 |
|
|
79 |
// Start handling the demands for the functions. |
|
80 |
// Validation for IN A records. Can only have an IP. Nothing else. |
82
|
81 |
if ($type == 'A' && !$ip4) { |
1
|
82 |
error(ERR_DNS_IPV4); |
82
|
83 |
return false; |
1
|
84 |
} |
|
85 |
|
82
|
86 |
if ($type == 'AAAA' && !$ip6) { |
1
|
87 |
error(ERR_DNS_IPV6); |
82
|
88 |
return false; |
1
|
89 |
} |
|
90 |
|
82
|
91 |
if ($type == 'CNAME' && $hostname) { |
|
92 |
if(!is_valid_cname($name)) { |
1
|
93 |
error(ERR_DNS_CNAME); |
82
|
94 |
return false; |
1
|
95 |
} |
|
96 |
} |
|
97 |
|
82
|
98 |
if ($type == 'NS') { |
1
|
99 |
$status = is_valid_ns($content, $hostname); |
82
|
100 |
if($status == -1) { |
1
|
101 |
error(ERR_DNS_NS_HNAME); |
82
|
102 |
return false; |
1
|
103 |
} |
82
|
104 |
elseif($status == -2) { |
1
|
105 |
error(ERR_DNS_NS_CNAME); |
82
|
106 |
return false; |
1
|
107 |
} |
|
108 |
} |
|
109 |
|
138
|
110 |
if ($type == 'SOA' && !is_valid_rr_soa($content)) { |
|
111 |
return false; |
1
|
112 |
} |
|
113 |
|
|
114 |
// HINFO and TXT require no validation. |
|
115 |
|
82
|
116 |
if ($type == 'URL') { |
|
117 |
if(!is_valid_url($content)) { |
1
|
118 |
error(ERR_INV_URL); |
82
|
119 |
return false; |
1
|
120 |
} |
|
121 |
} |
82
|
122 |
if ($type == 'MBOXFW') { |
|
123 |
if(!is_valid_mboxfw($content)) { |
1
|
124 |
error(ERR_INV_EMAIL); |
82
|
125 |
return false; |
1
|
126 |
} |
|
127 |
} |
|
128 |
|
|
129 |
// NAPTR has to be done. |
|
130 |
// Do we want that? |
|
131 |
// http://www.ietf.org/rfc/rfc2915.txt |
|
132 |
// http://www.zvon.org/tmRFC/RFC2915/Output/chapter2.html |
|
133 |
// http://www.zvon.org/tmRFC/RFC3403/Output/chapter4.html |
|
134 |
|
|
135 |
// See if the prio field is valid and if we have one. |
|
136 |
// If we dont have one and the type is MX record, give it value '10' |
82
|
137 |
if($type == 'NAPTR') { |
1
|
138 |
|
|
139 |
} |
|
140 |
|
82
|
141 |
if($type == 'MX') { |
|
142 |
if($hostname) { |
1
|
143 |
$status = is_valid_mx($content, $prio); |
82
|
144 |
if($status == -1) { |
1
|
145 |
error(ERR_DNS_MX_CNAME); |
82
|
146 |
return false; |
1
|
147 |
} |
82
|
148 |
elseif($status == -2) { |
1
|
149 |
error(ERR_DNS_MX_PRIO); |
82
|
150 |
return false; |
1
|
151 |
} |
82
|
152 |
} else { |
|
153 |
error( _('If you specify an MX record it must be a hostname.') ); // TODO make error |
|
154 |
return false; |
1
|
155 |
} |
82
|
156 |
} else { |
55
|
157 |
$prio=0; |
1
|
158 |
} |
|
159 |
// Validate the TTL, it has to be numeric. |
136
|
160 |
$ttl = (!isset($ttl) || !is_numeric($ttl)) ? $dns_ttl : $ttl; |
82
|
161 |
|
|
162 |
return true; |
1
|
163 |
} |
|
164 |
|
|
165 |
/* |
|
166 |
* Validatis a CNAME record by the name it will have and its destination |
|
167 |
* |
|
168 |
*/ |
|
169 |
function is_valid_cname($dest) |
|
170 |
{ |
|
171 |
/* |
|
172 |
* This is really EVIL. |
|
173 |
* If the new record (a CNAME) record is being pointed to by a MX record or NS record we have to bork. |
|
174 |
* this is the idea. |
|
175 |
* |
|
176 |
* MX record: blaat.nl MX mail.blaat.nl |
|
177 |
* Now we look what mail.blaat.nl is |
|
178 |
* We discover the following: |
|
179 |
* mail.blaat.nl CNAME bork.blaat.nl |
|
180 |
* This is NOT allowed! mail.onthanet.nl can not be a CNAME! |
|
181 |
* The same goes for NS. mail.blaat.nl must have a normal IN A record. |
|
182 |
* It MAY point to a CNAME record but its not wished. Lets not support it. |
|
183 |
*/ |
|
184 |
|
|
185 |
global $db; |
|
186 |
|
|
187 |
// Check if there are other records with this information of the following types. |
|
188 |
// P.S. we might add CNAME to block CNAME recursion and chains. |
|
189 |
$blockedtypes = " AND (type='MX' OR type='NS')"; |
|
190 |
|
65
|
191 |
$cnamec = "SELECT type, content FROM records WHERE content=".$db->quote($dest) . $blockedtypes; |
1
|
192 |
$result = $db->query($cnamec); |
|
193 |
|
|
194 |
if($result->numRows() > 0) |
|
195 |
{ |
|
196 |
return false; |
|
197 |
// Lets inform the user he is doing something EVIL. |
|
198 |
// Ok we found a record that has our content field in their content field. |
|
199 |
} |
|
200 |
return true; |
|
201 |
} |
|
202 |
|
|
203 |
|
|
204 |
/* |
|
205 |
* Checks if something is a valid domain. |
|
206 |
* Checks for domainname with the allowed characters <a,b,...z,A,B,...Z> and - and _. |
|
207 |
* This part must be followed by a 2 to 4 character TLD. |
|
208 |
*/ |
|
209 |
function is_valid_domain($domain) |
|
210 |
{ |
|
211 |
if ((eregi("^[0-9a-z]([-.]?[0-9a-z])*\\.[a-z]{2,4}$", $domain)) && (strlen($domain) <= 128)) |
|
212 |
{ |
|
213 |
return true; |
|
214 |
} |
|
215 |
return false; |
|
216 |
} |
|
217 |
|
|
218 |
|
|
219 |
/* |
|
220 |
* Validates if given hostname is allowed. |
|
221 |
* returns true if allowed. |
|
222 |
*/ |
|
223 |
function is_valid_hostname($host) |
|
224 |
{ |
|
225 |
if(count(explode(".", $host)) == 1) |
|
226 |
{ |
|
227 |
return false; |
|
228 |
} |
|
229 |
|
|
230 |
// Its not perfect (in_addr.int is allowed) but works for now. |
|
231 |
|
|
232 |
if(preg_match('!(ip6|in-addr).(arpa|int)$!i', $host)) |
|
233 |
{ |
|
234 |
if(preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host)) |
|
235 |
{ |
|
236 |
return true; |
|
237 |
} |
|
238 |
return false; |
|
239 |
} |
|
240 |
|
|
241 |
// Validate further. |
|
242 |
return (preg_match('!^(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+$!i', $host)) ? true : false; |
|
243 |
} |
|
244 |
|
|
245 |
|
|
246 |
/* |
|
247 |
* Validates an IPv4 IP. |
|
248 |
* returns true if valid. |
|
249 |
*/ |
|
250 |
function is_valid_ip($ip) |
|
251 |
{ |
|
252 |
// Stop reading at this point. Scroll down to the next function... |
|
253 |
// Ok... you didn't stop reading... now you have to rewrite the whole function! enjoy ;-) |
|
254 |
// Trance unborked it. Twice even! |
|
255 |
return ($ip == long2ip(ip2long($ip))) ? true : false; |
|
256 |
|
|
257 |
} |
|
258 |
|
|
259 |
|
|
260 |
/* |
|
261 |
* Validates an IPv6 IP. |
|
262 |
* returns true if valid. |
|
263 |
*/ |
|
264 |
function is_valid_ip6($ip) |
|
265 |
{ |
|
266 |
// Validates if the given IP is truly an IPv6 address. |
|
267 |
// Precondition: have a string |
|
268 |
// Postcondition: false: Error in IP |
|
269 |
// true: IP is correct |
|
270 |
// Requires: String |
|
271 |
// Date: 10-sep-2002 |
|
272 |
if(preg_match('!^[A-F0-9:]{1,39}$!i', $ip) == true) |
|
273 |
{ |
|
274 |
// Not 3 ":" or more. |
|
275 |
$p = explode(':::', $ip); |
|
276 |
if(sizeof($p) > 1) |
|
277 |
{ |
|
278 |
return false; |
|
279 |
} |
|
280 |
// Find if there is only one occurence of "::". |
|
281 |
$p = explode('::', $ip); |
|
282 |
if(sizeof($p) > 2) |
|
283 |
{ |
|
284 |
return false; |
|
285 |
} |
|
286 |
// Not more than 8 octects |
|
287 |
$p = explode(':', $ip); |
|
288 |
|
|
289 |
if(sizeof($p) > 8) |
|
290 |
{ |
|
291 |
return false; |
|
292 |
} |
|
293 |
|
|
294 |
// Check octet length |
|
295 |
foreach($p as $checkPart) |
|
296 |
{ |
|
297 |
if(strlen($checkPart) > 4) |
|
298 |
{ |
|
299 |
return false; |
|
300 |
} |
|
301 |
} |
|
302 |
return true; |
|
303 |
} |
|
304 |
return false; |
|
305 |
} |
|
306 |
|
|
307 |
|
|
308 |
/* |
|
309 |
* FANCY RECORD. |
|
310 |
* Validates if the fancy record mboxfw is an actual email address. |
|
311 |
*/ |
|
312 |
function is_valid_mboxfw($email) |
|
313 |
{ |
|
314 |
return is_valid_email($email); |
|
315 |
} |
|
316 |
|
|
317 |
|
|
318 |
/* |
|
319 |
* Validates MX records. |
|
320 |
* an MX record cant point to a CNAME record. This has to be checked. |
|
321 |
* this function also sets a proper priority. |
|
322 |
*/ |
|
323 |
function is_valid_mx($content, &$prio) |
|
324 |
{ |
|
325 |
global $db; |
|
326 |
// See if the destination to which this MX is pointing is NOT a CNAME record. |
|
327 |
// Check inside our dns server. |
65
|
328 |
if($db->queryOne("SELECT count(id) FROM records WHERE name=".$db->quote($content)." AND type='CNAME'") > 0) |
1
|
329 |
{ |
|
330 |
return -1; |
|
331 |
} |
|
332 |
else |
|
333 |
{ |
|
334 |
// Fix the proper priority for the record. |
|
335 |
// Bugfix, thanks Oscar :) |
|
336 |
if(!isset($prio)) |
|
337 |
{ |
|
338 |
$prio = 10; |
|
339 |
} |
|
340 |
if(!is_numeric($prio)) |
|
341 |
{ |
|
342 |
if($prio == '') |
|
343 |
{ |
|
344 |
$prio = 10; |
|
345 |
} |
|
346 |
else |
|
347 |
{ |
|
348 |
return -2; |
|
349 |
} |
|
350 |
} |
|
351 |
} |
|
352 |
return 1; |
|
353 |
} |
|
354 |
|
|
355 |
/* |
|
356 |
* Validates NS records. |
|
357 |
* an NS record cant point to a CNAME record. This has to be checked. |
|
358 |
* $hostname directive means if its a hostname or not (this to avoid that NS records get ip fields) |
|
359 |
* NS must have a hostname, it is not allowed to have an IP. |
|
360 |
*/ |
|
361 |
function is_valid_ns($content, $hostname) |
|
362 |
{ |
|
363 |
global $db; |
|
364 |
// Check if the field is a hostname, it MUST be a hostname. |
|
365 |
if(!$hostname) |
|
366 |
{ |
|
367 |
return -1; |
|
368 |
// "an IN NS field must be a hostname." |
|
369 |
} |
|
370 |
|
65
|
371 |
if($db->queryOne("SELECT count(id) FROM records WHERE name=".$db->quote($content)." AND type='CNAME'") > 0) |
1
|
372 |
{ |
|
373 |
return -2; |
|
374 |
// "You can not point a NS record to a CNAME record. Remove/rename the CNAME record first or take another name." |
|
375 |
|
|
376 |
} |
|
377 |
return 1; |
|
378 |
} |
|
379 |
|
|
380 |
|
138
|
381 |
function is_valid_hostname_label($hostname_label) { |
|
382 |
|
|
383 |
// See <https://www.poweradmin.org/trac/wiki/Documentation/DNS-hostnames>. |
|
384 |
if (!preg_match('/^[a-z\d]([a-z\d-]*[a-z\d])*$/i',$hostname_label)) { |
|
385 |
return false; |
|
386 |
} elseif (preg_match('/^[\d]+$/i',$hostname_label)) { |
|
387 |
return false; |
|
388 |
} elseif ((strlen($hostname_label) < 2) || (strlen($hostname_label) > 63)) { |
|
389 |
return false; |
|
390 |
} |
|
391 |
return true; |
|
392 |
} |
|
393 |
|
|
394 |
function is_valid_hostname_fqdn($hostname) { |
1
|
395 |
|
138
|
396 |
// See <https://www.poweradmin.org/trac/wiki/Documentation/DNS-hostnames>. |
|
397 |
global $dns_strict_tld_check; |
|
398 |
global $valid_tlds; |
|
399 |
|
|
400 |
$hostname = ereg_replace("\.$","",$hostname); |
1
|
401 |
|
138
|
402 |
if (strlen($hostname) > 255) { |
|
403 |
error(ERR_DNS_HN_TOO_LONG); |
|
404 |
return false; |
|
405 |
} |
|
406 |
|
|
407 |
$hostname_labels = explode ('.', $hostname); |
|
408 |
$label_count = count($hostname_labels); |
|
409 |
|
|
410 |
if ($dns_strict_tld_check == "1" && !in_array($hostname_labels[$label_count-1], $valid_tlds)) { |
|
411 |
error(ERR_DNS_INV_TLD); |
|
412 |
return false; |
1
|
413 |
} |
|
414 |
|
138
|
415 |
if ($hostname_labels[$label_count-1] == "arpa") { |
|
416 |
// FIXME |
|
417 |
} else { |
|
418 |
foreach ($hostname_labels as $hostname_label) { |
|
419 |
if (!is_valid_hostname_label($hostname_label)) { |
|
420 |
error(ERR_DNS_HOSTNAME); |
|
421 |
return false; |
|
422 |
} |
|
423 |
} |
|
424 |
} |
|
425 |
return true; |
|
426 |
} |
|
427 |
|
|
428 |
function is_valid_rr_soa(&$content) { |
1
|
429 |
|
138
|
430 |
// TODO move to appropiate location |
|
431 |
// $return = get_records_by_type_from_domid("SOA", $zid); |
|
432 |
// if($return->numRows() > 1) { |
|
433 |
// return false; |
|
434 |
// } |
|
435 |
|
|
436 |
$fields = preg_split("/\s+/", trim($content)); |
|
437 |
$field_count = count($fields); |
|
438 |
|
|
439 |
if ($field_count == 0 || $field_count > 7) { |
|
440 |
return false; |
|
441 |
} else { |
|
442 |
if (!is_valid_hostname_fqdn($fields[0]) || preg_match('/\.arpa\.?$/',$fields[0])) { |
|
443 |
return false; |
|
444 |
} |
|
445 |
$final_soa = $fields[0]; |
121
|
446 |
|
138
|
447 |
if (isset($fields[1])) { |
|
448 |
$addr_input = $fields[1]; |
|
449 |
} else { |
|
450 |
global $dns_hostmaster; |
|
451 |
$addr_input = $dns_hostmaster; |
|
452 |
} |
|
453 |
if (!preg_match("/@/", $addr_input)) { |
|
454 |
$addr_input = preg_split('/(?<!\\\)\./', $addr_input, 2); |
|
455 |
$addr_to_check = str_replace("\\", "", $addr_input[0]) . "@" . $addr_input[1]; |
|
456 |
} else { |
|
457 |
$addr_to_check = $addr_input; |
|
458 |
} |
|
459 |
|
|
460 |
if (!is_valid_email($addr_to_check)) { |
|
461 |
return false; |
|
462 |
} else { |
|
463 |
$addr_final = explode('@', $addr_to_check, 2); |
|
464 |
$final_soa .= " " . str_replace(".", "\\.", $addr_final[0]) . "." . $addr_final[1]; |
|
465 |
} |
|
466 |
|
|
467 |
if (isset($fields[2])) { |
|
468 |
if (!is_numeric($fields[2])) { |
|
469 |
return false; |
1
|
470 |
} |
138
|
471 |
$final_soa .= " " . $fields[2]; |
|
472 |
} else { |
|
473 |
$final_soa .= " 0"; |
|
474 |
} |
|
475 |
|
|
476 |
if ($field_count == 7) { |
|
477 |
for ($i = 3; ($i < 7); $i++) { |
|
478 |
if (!is_numeric($fields[$i])) { |
|
479 |
return false; |
121
|
480 |
} else { |
138
|
481 |
$final_soa .= " " . $fields[$i]; |
1
|
482 |
} |
|
483 |
} |
|
484 |
} |
|
485 |
} |
138
|
486 |
$content = $final_soa; |
|
487 |
return true; |
1
|
488 |
} |
|
489 |
|
|
490 |
|
|
491 |
function is_valid_url($url) |
|
492 |
{ |
|
493 |
return preg_match('!^(http://)(([A-Z\d]|[A-Z\d][A-Z\d-]*[A-Z\d])\.)*[A-Z\d]+([//]([0-9a-z//~#%&\'_\-+=:?.]*))?$!i', $url); |
|
494 |
} |
|
495 |
|
62
|
496 |
function is_valid_search($holygrail) |
|
497 |
{ |
|
498 |
// Only allow for alphanumeric, numeric, dot, dash, underscore and |
|
499 |
// percent in search string. The last two are wildcards for SQL. |
|
500 |
// Needs extension probably for more usual record types. |
|
501 |
|
|
502 |
return preg_match('/^[a-z0-9.\-%_]+$/i', $holygrail); |
|
503 |
} |
|
504 |
|
|
505 |
|
1
|
506 |
?> |