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