Changeset 544 for branches/stable
- Timestamp:
- 12/10/13 17:15:56 (11 years ago)
- Location:
- branches/stable
- Files:
-
- 11 edited
- 3 copied
Legend:
- Unmodified
- Added
- Removed
-
branches/stable
-
branches/stable/DNSDB.pm
r438 r544 28 28 use Crypt::PasswdMD5; 29 29 use Net::SMTP; 30 use NetAddr::IP ;30 use NetAddr::IP qw(:lower); 31 31 use POSIX; 32 32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); … … 39 39 &changeGroup 40 40 &loadConfig &connectDB &finish 41 &addDomain &delDomain &domainName &domainID 41 &addDomain &delDomain &domainName &revName &domainID &addRDNS 42 &getZoneCount &getZoneList 42 43 &addGroup &delGroup &getChildren &groupName 43 44 &addUser &updateUser &delUser &userFullName &userStatus &getUserData 44 45 &getSOA &getRecLine &getDomRecs &getRecCount 45 46 &addRec &updateRec &delRec 46 &getParents 47 &getTypelist 48 &parentID 47 49 &isParent 48 50 &domStatus &importAXFR … … 59 61 &changeGroup 60 62 &loadConfig &connectDB &finish 61 &addDomain &delDomain &domainName &domainID 63 &addDomain &delDomain &domainName &revName &domainID &addRDNS 64 &getZoneCount &getZoneList 62 65 &addGroup &delGroup &getChildren &groupName 63 66 &addUser &updateUser &delUser &userFullName &userStatus &getUserData 64 67 &getSOA &getRecLine &getDomRecs &getRecCount 65 68 &addRec &updateRec &delRec 66 &getParents 69 &getTypelist 70 &parentID 67 71 &isParent 68 72 &domStatus &importAXFR … … 139 143 perpage => 15, 140 144 ); 145 146 ## (Semi)private variables 147 # Hash of functions for validating record types. Filled in initGlobals() since 148 # it relies on visibility flags from the rectypes table in the DB 149 my %validators; 150 151 152 ## 153 ## utility functions 154 # _rectable() 155 # Takes default+rdns flags, returns appropriate table name 156 sub _rectable { 157 my $def = shift; 158 my $rev = shift; 159 160 return 'records' if $def ne 'y'; 161 return 'default_records' if $rev ne 'y'; 162 return 'default_rev_records'; 163 } # end _rectable() 164 165 # _recparent() 166 # Takes default+rdns flags, returns appropriate parent-id column name 167 sub _recparent { 168 my $def = shift; 169 my $rev = shift; 170 171 return 'group_id' if $def eq 'y'; 172 return 'rdns_id' if $rev eq 'y'; 173 return 'domain_id'; 174 } # end _recparent() 175 176 # Check an IP to be added in a reverse zone to see if it's really in the requested parent. 177 # Takes a database handle, default and reverse flags, IP (fragment) to check, parent zone ID, 178 # and a reference to a NetAddr::IP object (also used to pass back a fully-reconstructed IP for 179 # database insertion) 180 sub _ipparent { 181 my $dbh = shift; 182 my $defrec = shift; 183 my $revrec = shift; 184 my $val = shift; 185 my $id = shift; 186 my $addr = shift; 187 188 return if $revrec ne 'y'; # this sub not useful in forward zones 189 190 $$addr = NetAddr::IP->new($$val); #necessary? 191 192 # subsub to split, reverse, and overlay an IP fragment on a netblock 193 sub __rev_overlay { 194 my $splitme = shift; # ':' or '.', m'lud? 195 my $parnet = shift; 196 my $val = shift; 197 my $addr = shift; 198 199 my $joinme = $splitme; 200 $splitme = '\.' if $splitme eq '.'; 201 my @working = reverse(split($splitme, $parnet->addr)); 202 my @parts = reverse(split($splitme, $$val)); 203 for (my $i = 0; $i <= $#parts; $i++) { 204 $working[$i] = $parts[$i]; 205 } 206 my $checkme = NetAddr::IP->new(join($joinme, reverse(@working))) or return 0; 207 return 0 unless $checkme->within($parnet); 208 $$addr = $checkme; # force "correct" IP to be recorded. 209 return 1; 210 } 211 212 my ($parstr) = $dbh->selectrow_array("SELECT revnet FROM revzones WHERE rdns_id = ?", undef, ($id)); 213 my $parnet = NetAddr::IP->new($parstr); 214 215 # Fail early on v6-in-v4 or v4-in-v6. We're not accepting these ATM. 216 return 0 if $parnet->addr =~ /\./ && $$val =~ /:/; 217 return 0 if $parnet->addr =~ /:/ && $$val =~ /\./; 218 219 if ($$addr && $$val =~ /^[\da-fA-F][\da-fA-F:]+[\da-fA-F]$/) { 220 # the only case where NetAddr::IP's acceptance of legitimate IPs is "correct" is for a proper IPv6 address. 221 # the rest we have to restructure before fiddling. *sigh* 222 return 1 if $$addr->within($parnet); 223 } else { 224 # We don't have a complete IP in $$val (yet) 225 if ($parnet->addr =~ /:/) { 226 $$val =~ s/^:+//; # gotta strip'em all... 227 return __rev_overlay(':', $parnet, $val, $addr); 228 } 229 if ($parnet->addr =~ /\./) { 230 $$val =~ s/^\.+//; 231 return __rev_overlay('.', $parnet, $val, $addr); 232 } 233 # should be impossible to get here... 234 } 235 # ... and here. 236 # can't do nuttin' in forward zones 237 } # end _ipparent() 238 239 # A little different than _ipparent above; this tries to *find* the parent zone of a hostname 240 sub _hostparent { 241 my $dbh = shift; 242 my $hname = shift; 243 244 my @hostbits = split /\./, $hname; 245 my $sth = $dbh->prepare("SELECT count(*),domain_id FROM domains WHERE domain = ? GROUP BY domain_id"); 246 foreach (@hostbits) { 247 $sth->execute($hname); 248 my ($found, $parid) = $sth->fetchrow_array; 249 if ($found) { 250 return $parid; 251 } 252 $hname =~ s/^$_\.//; 253 } 254 } # end _hostparent() 255 256 ## 257 ## Record validation subs. 258 ## 259 260 # A record 261 sub _validate_1 { 262 my $dbh = shift; 263 264 my %args = @_; 265 266 return ('FAIL', 'Reverse zones cannot contain A records') if $args{revrec} eq 'y'; 267 268 # Coerce all hostnames to end in ".DOMAIN" for group/default records, 269 # or the intended parent domain for live records. 270 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 271 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/; 272 273 # Check IP is well-formed, and that it's a v4 address 274 # Fail on "compact" IPv4 variants, because they are not consistent and predictable. 275 return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address") 276 unless ${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/; 277 return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv4 address") 278 unless $args{addr} && !$args{addr}->{isv6}; 279 # coerce IP/value to normalized form for storage 280 ${$args{val}} = $args{addr}->addr; 281 282 return ('OK','OK'); 283 } # done A record 284 285 # NS record 286 sub _validate_2 { 287 my $dbh = shift; 288 289 my %args = @_; 290 291 # Coerce the hostname to "DOMAIN" for forward default records, "ZONE" for reverse default records, 292 # or the intended parent zone for live records. 293 ##fixme: allow for delegating <subdomain>.DOMAIN? 294 if ($args{revrec} eq 'y') { 295 my $pname = ($args{defrec} eq 'y' ? 'ZONE' : revName($dbh,$args{id})); 296 ${$args{host}} = $pname if ${$args{host}} ne $pname; 297 } else { 298 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 299 ${$args{host}} = $pname if ${$args{host}} ne $pname; 300 } 301 302 # Let this lie for now. Needs more magic. 303 # # Check IP is well-formed, and that it's a v4 address 304 # return ('FAIL',"A record must be a valid IPv4 address") 305 # unless $addr && !$addr->{isv6}; 306 # # coerce IP/value to normalized form for storage 307 # $$val = $addr->addr; 308 309 return ('OK','OK'); 310 } # done NS record 311 312 # CNAME record 313 sub _validate_5 { 314 my $dbh = shift; 315 316 my %args = @_; 317 318 # Not really true, but these are only useful for delegating smaller-than-/24 IP blocks. 319 # This is fundamentally a messy operation and should really just be taken care of by the 320 # export process, not manual maintenance of the necessary records. 321 return ('FAIL', 'Reverse zones cannot contain CNAME records') if $args{revrec} eq 'y'; 322 323 # Coerce all hostnames to end in ".DOMAIN" for group/default records, 324 # or the intended parent domain for live records. 325 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 326 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/; 327 328 return ('OK','OK'); 329 } # done CNAME record 330 331 # SOA record 332 sub _validate_6 { 333 # Smart monkeys won't stick their fingers in here; we have 334 # separate dedicated routines to deal with SOA records. 335 return ('OK','OK'); 336 } # done SOA record 337 338 # PTR record 339 sub _validate_12 { 340 my $dbh = shift; 341 342 my %args = @_; 343 344 if ($args{revrec} eq 'y') { 345 if ($args{defrec} eq 'n') { 346 return ('FAIL', "IP or IP fragment ${$args{val}} is not within ".revName($dbh, $args{id})) 347 unless _ipparent($dbh, $args{defrec}, $args{revrec}, $args{val}, $args{id}, \$args{addr}); 348 ${$args{val}} = $args{addr}->addr; 349 } else { 350 if (${$args{val}} =~ /\./) { 351 # looks like a v4 or fragment 352 if (${$args{val}} =~ /^\d+\.\d+\.\d+\.\d+$/) { 353 # woo! a complete IP! validate it and normalize, or fail. 354 $args{addr} = NetAddr::IP->new(${$args{val}}) 355 or return ('FAIL', "IP/value looks like IPv4 but isn't valid"); 356 ${$args{val}} = $args{addr}->addr; 357 } else { 358 ${$args{val}} =~ s/^\.*/ZONE./ unless ${$args{val}} =~ /^ZONE/; 359 } 360 } elsif (${$args{val}} =~ /[a-f:]/) { 361 # looks like a v6 or fragment 362 ${$args{val}} =~ s/^:*/ZONE::/ if !$args{addr} && ${$args{val}} !~ /^ZONE/; 363 if ($args{addr}) { 364 if ($args{addr}->addr =~ /^0/) { 365 ${$args{val}} =~ s/^:*/ZONE::/ unless ${$args{val}} =~ /^ZONE/; 366 } else { 367 ${$args{val}} = $args{addr}->addr; 368 } 369 } 370 } else { 371 # bare number (probably). These could be v4 or v6, so we'll 372 # expand on these on creation of a reverse zone. 373 ${$args{val}} = "ZONE,${$args{val}}" unless ${$args{val}} =~ /^ZONE/; 374 } 375 ${$args{host}} =~ s/\.*$/\.$config{domain}/ if ${$args{host}} !~ /(?:$config{domain}|ADMINDOMAIN)$/; 376 } 377 378 # Multiple PTR records do NOT generally do what most people believe they do, 379 # and tend to fail in the most awkward way possible. Check and warn. 380 # We use $val instead of $addr->addr since we may be in a defrec, and may have eg "ZONE::42" or "ZONE.12" 381 382 my @checkvals = (${$args{val}}); 383 if (${$args{val}} =~ /,/) { 384 # push . and :: variants into checkvals if val has , 385 my $tmp; 386 ($tmp = ${$args{val}}) =~ s/,/./; 387 push @checkvals, $tmp; 388 ($tmp = ${$args{val}}) =~ s/,/::/; 389 push @checkvals, $tmp; 390 } 391 my $pcsth = $dbh->prepare("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec})." WHERE val = ?"); 392 foreach my $checkme (@checkvals) { 393 my $ptrcount; 394 ($ptrcount) = $dbh->selectrow_array("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec}). 395 " WHERE val = ?", undef, ($checkme)); 396 return ('WARN', "PTR record for $checkme already exists; adding another will probably not do what you want") 397 if $ptrcount; 398 } 399 } else { 400 # Not absolutely true but only useful if you hack things up for sub-/24 v4 reverse delegations 401 # Simpler to just create the reverse zone and grant access for the customer to edit it, and create direct 402 # PTR records on export 403 return ('FAIL',"Forward zones cannot contain PTR records"); 404 } 405 406 return ('OK','OK'); 407 } # done PTR record 408 409 # MX record 410 sub _validate_15 { 411 my $dbh = shift; 412 413 my %args = @_; 414 415 # Not absolutely true but WTF use is an MX record for a reverse zone? 416 return ('FAIL', 'Reverse zones cannot contain MX records') if $args{revrec} eq 'y'; 417 418 return ('FAIL', "Distance is required for MX records") unless defined(${$args{dist}}); 419 ${$args{dist}} =~ s/\s*//g; 420 return ('FAIL',"Distance is required, and must be numeric") unless ${$args{dist}} =~ /^\d+$/; 421 422 ${$args{fields}} = "distance,"; 423 push @{$args{vallist}}, ${$args{dist}}; 424 425 # Coerce all hostnames to end in ".DOMAIN" for group/default records, 426 # or the intended parent domain for live records. 427 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 428 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/; 429 430 return ('OK','OK'); 431 } # done MX record 432 433 # TXT record 434 sub _validate_16 { 435 # Could arguably put a WARN return here on very long (>512) records 436 return ('OK','OK'); 437 } # done TXT record 438 439 # RP record 440 sub _validate_17 { 441 # Probably have to validate these some day 442 return ('OK','OK'); 443 } # done RP record 444 445 # AAAA record 446 sub _validate_28 { 447 my $dbh = shift; 448 449 my %args = @_; 450 451 return ('FAIL', 'Reverse zones cannot contain AAAA records') if $args{revrec} eq 'y'; 452 453 # Coerce all hostnames to end in ".DOMAIN" for group/default records, 454 # or the intended parent domain for live records. 455 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 456 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/; 457 458 # Check IP is well-formed, and that it's a v6 address 459 return ('FAIL',"$typemap{${$args{rectype}}} record must be a valid IPv6 address") 460 unless $args{addr} && $args{addr}->{isv6}; 461 # coerce IP/value to normalized form for storage 462 ${$args{val}} = $args{addr}->addr; 463 464 return ('OK','OK'); 465 } # done AAAA record 466 467 # SRV record 468 sub _validate_33 { 469 my $dbh = shift; 470 471 my %args = @_; 472 473 # Not absolutely true but WTF use is an SRV record for a reverse zone? 474 return ('FAIL', 'Reverse zones cannot contain SRV records') if $args{revrec} eq 'y'; 475 476 return ('FAIL', "Distance is required for SRV records") unless defined(${$args{dist}}); 477 ${$args{dist}} =~ s/\s*//g; 478 return ('FAIL',"Distance is required, and must be numeric") unless ${$args{dist}} =~ /^\d+$/; 479 480 return ('FAIL',"SRV records must begin with _service._protocol [${$args{host}}]") 481 unless ${$args{host}} =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/; 482 return ('FAIL',"Port and weight are required for SRV records") 483 unless defined(${$args{weight}}) && defined(${$args{port}}); 484 ${$args{weight}} =~ s/\s*//g; 485 ${$args{port}} =~ s/\s*//g; 486 487 return ('FAIL',"Port and weight are required, and must be numeric") 488 unless ${$args{weight}} =~ /^\d+$/ && ${$args{port}} =~ /^\d+$/; 489 490 ${$args{fields}} = "distance,weight,port,"; 491 push @{$args{vallist}}, (${$args{dist}}, ${$args{weight}}, ${$args{port}}); 492 493 # Coerce all hostnames to end in ".DOMAIN" for group/default records, 494 # or the intended parent domain for live records. 495 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$args{id})); 496 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/; 497 498 return ('OK','OK'); 499 } # done SRV record 500 501 # Now the custom types 502 503 # A+PTR record. With a very little bit of magic we can also use this sub to validate AAAA+PTR. Whee! 504 sub _validate_65280 { 505 my $dbh = shift; 506 507 my %args = @_; 508 509 my $code = 'OK'; 510 my $msg = 'OK'; 511 512 if ($args{defrec} eq 'n') { 513 # live record; revrec determines whether we validate the PTR or A component first. 514 515 if ($args{revrec} eq 'y') { 516 ($code,$msg) = _validate_12($dbh, %args); 517 return ($code,$msg) if $code eq 'FAIL'; 518 519 # Check if the reqested domain exists. If not, coerce the type down to PTR and warn. 520 if (!(${$args{domid}} = _hostparent($dbh, ${$args{host}}))) { 521 my $addmsg = "Record added as PTR instead of $typemap{${$args{rectype}}}; domain not found for ${$args{host}}"; 522 $msg .= "\n$addmsg" if $code eq 'WARN'; 523 $msg = $addmsg if $code eq 'OK'; 524 ${$args{rectype}} = $reverse_typemap{PTR}; 525 return ('WARN', $msg); 526 } 527 528 # Add domain ID to field list and values 529 ${$args{fields}} .= "domain_id,"; 530 push @{$args{vallist}}, ${$args{domid}}; 531 532 } else { 533 ($code,$msg) = _validate_1($dbh, %args) if ${$args{rectype}} == 65280; 534 ($code,$msg) = _validate_28($dbh, %args) if ${$args{rectype}} == 65281; 535 return ($code,$msg) if $code eq 'FAIL'; 536 537 # Check if the requested reverse zone exists - note, an IP fragment won't 538 # work here since we don't *know* which parent to put it in. 539 # ${$args{val}} has been validated as a valid IP by now, in one of the above calls. 540 my ($revid) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?". 541 " ORDER BY masklen(revnet) DESC", undef, (${$args{val}})); 542 if (!$revid) { 543 $msg = "Record added as ".(${$args{rectype}} == 65280 ? 'A' : 'AAAA'). 544 " instead of $typemap{${$args{rectype}}}; reverse zone not found for ${$args{val}}"; 545 ${$args{rectype}} = (${$args{rectype}} == 65280 ? $reverse_typemap{A} : $reverse_typemap{AAAA}); 546 return ('WARN', $msg); 547 } 548 549 # Check for duplicate PTRs. Note we don't have to play games with $code and $msg, because 550 # by definition there can't be duplicate PTRs if the reverse zone isn't managed here. 551 my ($ptrcount) = $dbh->selectrow_array("SELECT count(*) FROM "._rectable($args{defrec},$args{revrec}). 552 " WHERE val = ?", undef, ${$args{val}}); 553 if ($ptrcount) { 554 $msg = "PTR record for ${$args{val}} already exists; adding another will probably not do what you want"; 555 $code = 'WARN'; 556 } 557 558 ${$args{fields}} .= "rdns_id,"; 559 push @{$args{vallist}}, $revid; 560 } 561 562 } else { # defrec eq 'y' 563 if ($args{revrec} eq 'y') { 564 ($code,$msg) = _validate_12($dbh, %args); 565 return ($code,$msg) if $code eq 'FAIL'; 566 if (${$args{rectype}} == 65280) { 567 return ('FAIL',"A+PTR record must be a valid IPv4 address or fragment") 568 if ${$args{val}} =~ /:/; 569 ${$args{val}} =~ s/^ZONE,/ZONE./; # Clean up after uncertain IP-fragment-type from _validate_12 570 } elsif (${$args{rectype}} == 65281) { 571 return ('FAIL',"AAAA+PTR record must be a valid IPv6 address or fragment") 572 if ${$args{val}} =~ /\./; 573 ${$args{val}} =~ s/^ZONE,/ZONE::/; # Clean up after uncertain IP-fragment-type from _validate_12 574 } 575 } else { 576 # This is easy. I also can't see a real use-case for A/AAAA+PTR in *all* forward 577 # domains, since you wouldn't be able to substitute both domain and reverse zone 578 # sanely, and you'd end up with guaranteed over-replicated PTR records that would 579 # confuse the hell out of pretty much anything that uses them. 580 ##fixme: make this a config flag? 581 return ('FAIL', "$typemap{${$args{rectype}}} records not allowed in default domains"); 582 } 583 } 584 585 return ($code, $msg); 586 } # done A+PTR record 587 588 # AAAA+PTR record 589 # A+PTR above has been magicked to handle AAAA+PTR as well. 590 sub _validate_65281 { 591 return _validate_65280(@_); 592 } # done AAAA+PTR record 593 594 # PTR template record 595 sub _validate_65282 { 596 return ('OK','OK'); 597 } # done PTR template record 598 599 # A+PTR template record 600 sub _validate_65283 { 601 return ('OK','OK'); 602 } # done AAAA+PTR template record 603 604 # AAAA+PTR template record 605 sub _validate_65284 { 606 return ('OK','OK'); 607 } # done AAAA+PTR template record 608 141 609 142 610 … … 327 795 328 796 # load record types from database 329 my $sth = $dbh->prepare(" select val,name fromrectypes");797 my $sth = $dbh->prepare("SELECT val,name,stdflag FROM rectypes"); 330 798 $sth->execute; 331 while (my ($recval,$recname ) = $sth->fetchrow_array()) {799 while (my ($recval,$recname,$stdflag) = $sth->fetchrow_array()) { 332 800 $typemap{$recval} = $recname; 333 801 $reverse_typemap{$recname} = $recval; 802 # now we fill the record validation function hash 803 if ($stdflag < 5) { 804 my $fn = "_validate_$recval"; 805 $validators{$recval} = \&$fn; 806 } else { 807 my $fn = "sub { return ('FAIL','Type $recval ($recname) not supported'); }"; 808 $validators{$recval} = eval $fn; 809 } 334 810 } 335 811 } # end initGlobals … … 534 1010 # Log an action 535 1011 # Internal sub 536 # Takes a database handle, domain_id, user_id, group_id, email, name and log entry 1012 # Takes a database handle and log entry hash containing at least: 1013 # user_id, group_id, log entry 1014 # and optionally one or more of: 1015 # username/email, user full name, domain_id, rdns_id 537 1016 ##fixme: convert to trailing hash for user info 538 1017 # User info must contain a (user ID OR username)+fullname 539 1018 sub _log { 540 1019 my $dbh = shift; 541 my ($domain_id,$user_id,$group_id,$username,$name,$entry) = @_; 1020 1021 my %args = @_; 1022 1023 $args{rdns_id} = 0 if !$args{rdns_id}; 1024 $args{domain_id} = 0 if !$args{domain_id}; 542 1025 543 1026 ##fixme: need better way(s?) to snag userinfo for log entries. don't want to have 544 1027 # to pass around yet *another* constant (already passing $dbh, shouldn't need to) 545 1028 my $fullname; 546 if (!$user_id) { 547 ($user_id, $fullname) = $dbh->selectrow_array("SELECT user_id, firstname || ' ' || lastname FROM users". 548 " WHERE username=?", undef, ($username)); 549 } elsif (!$username) { 550 ($username, $fullname) = $dbh->selectrow_array("SELECT username, firstname || ' ' || lastname FROM users". 551 " WHERE user_id=?", undef, ($user_id)); 552 } else { 1029 if (!$args{user_id}) { 1030 ($args{user_id}, $fullname) = $dbh->selectrow_array("SELECT user_id, firstname || ' ' || lastname FROM users". 1031 " WHERE username=?", undef, ($args{username})); 1032 } 1033 if (!$args{username}) { 1034 ($args{username}, $fullname) = $dbh->selectrow_array("SELECT username, firstname || ' ' || lastname FROM users". 1035 " WHERE user_id=?", undef, ($args{user_id})); 1036 } 1037 if (!$args{fullname}) { 553 1038 ($fullname) = $dbh->selectrow_array("SELECT firstname || ' ' || lastname FROM users". 554 " WHERE user_id=?", undef, ($ user_id));555 } 556 557 $ name = $fullname if !$name;1039 " WHERE user_id=?", undef, ($args{user_id})); 1040 } 1041 1042 $args{name} = $fullname if !$args{name}; 558 1043 559 1044 ##fixme: farm out the actual logging to different subs for file, syslog, internal, etc based on config 560 $dbh->do("INSERT INTO log (domain_id, user_id,group_id,email,name,entry) VALUES (?,?,?,?,?,?)", undef,561 ($domain_id,$user_id,$group_id,$username,$name,$entry));562 # 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 563 # 1 2 3 4 5 6 7 1045 $dbh->do("INSERT INTO log (domain_id,rdns_id,user_id,group_id,email,name,entry) VALUES (?,?,?,?,?,?,?)", 1046 undef, 1047 ($args{domain_id},$args{rdns_id},$args{user_id},$args{group_id},$args{username},$args{name},$args{entry})); 1048 564 1049 } # end _log 565 1050 … … 619 1104 ($dom_id) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE domain=?", undef, ($domain)); 620 1105 621 _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname},622 "Added ".($state ? 'active' : 'inactive')." domain $domain");1106 _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group, username => $userinfo{username}, 1107 entry => "Added ".($state ? 'active' : 'inactive')." domain $domain")); 623 1108 624 1109 # ... and now we construct the standard records from the default set. NB: group should be variable. … … 634 1119 my @tmp1 = split /:/, $host; 635 1120 my @tmp2 = split /:/, $val; 636 _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname}, 1121 _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group, 1122 username => $userinfo{username}, entry => 637 1123 "[new $domain] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ". 638 "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl") ;1124 "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl")); 639 1125 } else { 640 1126 my $logentry = "[new $domain] Added record '$host $typemap{$type}"; 641 1127 $logentry .= " [distance $dist]" if $typemap{$type} eq 'MX'; 642 1128 $logentry .= " [priority $dist] [weight $weight] [port $port]" if $typemap{$type} eq 'SRV'; 643 _log($dbh, $dom_id, $userinfo{id}, $group, $userinfo{name}, $userinfo{fullname}, 644 $logentry." $val', TTL $ttl"); 1129 _log($dbh, (domain_id => $dom_id, user_id => $userinfo{id}, group_id => $group, 1130 username => $userinfo{username}, entry => 1131 $logentry." $val', TTL $ttl")); 645 1132 } 646 1133 } … … 713 1200 714 1201 1202 ## DNSDB::revName() 1203 # Return the reverse zone name based on an rDNS ID 1204 # Takes a database handle and the rDNS ID 1205 # Returns the reverse zone name or undef on failure 1206 sub revName { 1207 $errstr = ''; 1208 my $dbh = shift; 1209 my $revid = shift; 1210 my ($revname) = $dbh->selectrow_array("SELECT revnet FROM revzones WHERE rdns_id=?", undef, ($revid) ); 1211 $errstr = $DBI::errstr if !$revname; 1212 return $revname if $revname; 1213 } # end revName() 1214 1215 715 1216 ## DNSDB::domainID() 716 1217 # Takes a database handle and domain name … … 724 1225 return $domid if $domid; 725 1226 } # end domainID() 1227 1228 1229 ## DNSDB::addRDNS 1230 # Adds a reverse DNS zone 1231 # Takes a database handle, CIDR block, numeric group, boolean(ish) state (active/inactive), 1232 # and user info hash (for logging). 1233 # Returns a status code and message 1234 sub addRDNS { 1235 my $dbh = shift; 1236 my $zone = NetAddr::IP->new(shift); 1237 return ('FAIL',"Zone name must be a valid CIDR netblock") unless ($zone && $zone->addr !~ /^0/); 1238 my $revpatt = shift; 1239 my $group = shift; 1240 my $state = shift; 1241 1242 my %userinfo = @_; # remaining bits. 1243 # user ID, username, user full name 1244 1245 $state = 1 if $state =~ /^active$/; 1246 $state = 1 if $state =~ /^on$/; 1247 $state = 0 if $state =~ /^inactive$/; 1248 $state = 0 if $state =~ /^off$/; 1249 1250 return ('FAIL',"Invalid zone status") if $state !~ /^\d+$/; 1251 1252 # quick check to start to see if we've already got one 1253 my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revzone=?", undef, ("$zone")); 1254 1255 return ('FAIL', "Zone already exists") if $rdns_id; 1256 1257 # Allow transactions, and raise an exception on errors so we can catch it later. 1258 # Use local to make sure these get "reset" properly on exiting this block 1259 local $dbh->{AutoCommit} = 0; 1260 local $dbh->{RaiseError} = 1; 1261 1262 #$dbh->selectrow_array("SELECT currval('users_user_id_seq')"); 1263 # Wrap all the SQL in a transaction 1264 eval { 1265 # insert the domain... 1266 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,?,?)", undef, ($zone, $group, $state)); 1267 1268 # get the ID... 1269 ($rdns_id) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')"); 1270 1271 _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group, username => $userinfo{name}, 1272 entry => "Added ".($state ? 'active' : 'inactive')." reverse zone $zone")); 1273 1274 # ... and now we construct the standard records from the default set. NB: group should be variable. 1275 my $sth = $dbh->prepare("SELECT host,type,val,ttl FROM default_rev_records WHERE group_id=?"); 1276 my $sth_in = $dbh->prepare("INSERT INTO records (rdns_id,host,type,val,ttl)". 1277 " VALUES ($rdns_id,?,?,?,?)"); 1278 $sth->execute($group); 1279 while (my ($host,$type,$val,$ttl) = $sth->fetchrow_array()) { 1280 $host =~ s/ADMINDOMAIN/$config{domain}/g; 1281 ##work 1282 # - replace ZONE in $val 1283 # - skip records not appropriate for the zone (skip A+PTR on v6 zones, and AAAA+PTR on v4 zones) 1284 # $val =~ s/DOMAIN/$domain/g; 1285 $sth_in->execute($host,$type,$val,$ttl); 1286 if ($typemap{$type} eq 'SOA') { 1287 my @tmp1 = split /:/, $host; 1288 my @tmp2 = split /:/, $val; 1289 _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group, 1290 username => $userinfo{name}, entry => 1291 "[new $zone] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ". 1292 "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl")); 1293 } else { 1294 my $logentry = "[new $zone] Added record '$host $typemap{$type}"; 1295 # $logentry .= " [distance $dist]" if $typemap{$type} eq 'MX'; 1296 # $logentry .= " [priority $dist] [weight $weight] [port $port]" if $typemap{$type} eq 'SRV'; 1297 _log($dbh, (rdns_id => $rdns_id, user_id => $userinfo{id}, group_id => $group, 1298 username => $userinfo{name}, entry => 1299 $logentry." $val', TTL $ttl")); 1300 } 1301 } 1302 1303 # once we get here, we should have suceeded. 1304 $dbh->commit; 1305 }; # end eval 1306 1307 if ($@) { 1308 my $msg = $@; 1309 eval { $dbh->rollback; }; 1310 return ('FAIL',$msg); 1311 } else { 1312 return ('OK',$rdns_id); 1313 } 1314 1315 } # end addRDNS() 1316 1317 1318 ## DNSDB::getZoneCount 1319 # Get count of zones in group or groups 1320 # Takes a database handle and hash containing: 1321 # - the "current" group 1322 # - an array of "acceptable" groups 1323 # - a flag for forward/reverse zones 1324 # - Optionally accept a "starts with" and/or "contains" filter argument 1325 # Returns an integer count of the resulting zone list. 1326 sub getZoneCount { 1327 my $dbh = shift; 1328 1329 my %args = @_; 1330 1331 my @filterargs; 1332 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 1333 push @filterargs, "^$args{startwith}" if $args{startwith}; 1334 $args{filter} =~ s/\./\[\.\]/g if $args{filter}; # only match literal dots, usually in reverse zones 1335 push @filterargs, $args{filter} if $args{filter}; 1336 1337 my $sql; 1338 # Not as compact, and fix-me-twice if the common bits get wrong, but much easier to read 1339 if ($args{revrec} eq 'n') { 1340 $sql = "SELECT count(*) FROM domains". 1341 " WHERE group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")". 1342 ($args{startwith} ? " AND domain ~* ?" : ''). 1343 ($args{filter} ? " AND domain ~* ?" : ''); 1344 } else { 1345 $sql = "SELECT count(*) FROM revzones". 1346 " WHERE group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")". 1347 ($args{startwith} ? " AND CAST(revnet AS VARCHAR) ~* ?" : ''). 1348 ($args{filter} ? " AND CAST(revnet AS VARCHAR) ~* ?" : ''); 1349 } 1350 my ($count) = $dbh->selectrow_array($sql, undef, @filterargs); 1351 return $count; 1352 } # end getZoneCount() 1353 1354 1355 ## DNSDB::getZoneList() 1356 # Get a list of zones in the specified group(s) 1357 # Takes the same arguments as getZoneCount() above 1358 # Returns a reference to an array of hashrefs suitable for feeding to HTML::Template 1359 sub getZoneList { 1360 my $dbh = shift; 1361 1362 my %args = @_; 1363 1364 my @zonelist; 1365 1366 $args{sortorder} = 'ASC' if !grep $args{sortorder}, ('ASC','DESC'); 1367 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 1368 1369 my @filterargs; 1370 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 1371 push @filterargs, "^$args{startwith}" if $args{startwith}; 1372 $args{filter} =~ s/\./\[\.\]/g if $args{filter}; # only match literal dots, usually in reverse zones 1373 push @filterargs, $args{filter} if $args{filter}; 1374 1375 my $sql; 1376 # Not as compact, and fix-me-twice if the common bits get wrong, but much easier to read 1377 if ($args{revrec} eq 'n') { 1378 $args{sortby} = 'domain' if !grep $args{sortby}, ('revnet','group','status'); 1379 $sql = "SELECT domain_id,domain,status,groups.group_name AS group FROM domains". 1380 " INNER JOIN groups ON domains.group_id=groups.group_id". 1381 " WHERE domains.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")". 1382 ($args{startwith} ? " AND domain ~* ?" : ''). 1383 ($args{filter} ? " AND domain ~* ?" : ''); 1384 } else { 1385 ##fixme: arguably startwith here is irrelevant. depends on the UI though. 1386 $args{sortby} = 'revnet' if !grep $args{sortby}, ('domain','group','status'); 1387 $sql = "SELECT rdns_id,revnet,status,groups.group_name AS group FROM revzones". 1388 " INNER JOIN groups ON revzones.group_id=groups.group_id". 1389 " WHERE revzones.group_id IN ($args{curgroup}".($args{childlist} ? ",$args{childlist}" : '').")". 1390 ($args{startwith} ? " AND CAST(revnet AS VARCHAR) ~* ?" : ''). 1391 ($args{filter} ? " AND CAST(revnet AS VARCHAR) ~* ?" : ''); 1392 } 1393 # A common tail. 1394 $sql .= " ORDER BY ".($args{sortby} eq 'group' ? 'groups.group_name' : $args{sortby})." $args{sortorder} ". 1395 ($args{offset} eq 'all' ? '' : " LIMIT $config{perpage}". 1396 " OFFSET ".$args{offset}*$config{perpage}); 1397 my $sth = $dbh->prepare($sql); 1398 $sth->execute(@filterargs); 1399 my $rownum = 0; 1400 1401 while (my @data = $sth->fetchrow_array) { 1402 my %row; 1403 $row{domainid} = $data[0]; 1404 $row{domain} = $data[1]; 1405 $row{status} = $data[2]; 1406 $row{group} = $data[3]; 1407 push @zonelist, \%row; 1408 } 1409 1410 return \@zonelist; 1411 } # end getZoneList() 726 1412 727 1413 … … 762 1448 $sth->execute($pargroup,$groupname); 763 1449 764 $sth = $dbh->prepare("SELECT group_id FROM groups WHERE group_name=?"); 765 $sth->execute($groupname); 766 my ($groupid) = $sth->fetchrow_array(); 1450 my ($groupid) = $dbh->selectrow_array("SELECT group_id FROM groups WHERE group_name=?", undef, ($groupname)); 767 1451 768 1452 # Permissions … … 789 1473 790 1474 # Default records 791 $sth= $dbh->prepare("INSERT INTO default_records (group_id,host,type,val,distance,weight,port,ttl) ".1475 my $sthf = $dbh->prepare("INSERT INTO default_records (group_id,host,type,val,distance,weight,port,ttl) ". 792 1476 "VALUES ($groupid,?,?,?,?,?,?,?)"); 1477 my $sthr = $dbh->prepare("INSERT INTO default_rev_records (group_id,host,type,val,ttl) ". 1478 "VALUES ($groupid,?,?,?,?)"); 793 1479 if ($inherit) { 794 1480 # Duplicate records from parent. Actually relying on inherited records feels … … 797 1483 $sth2->execute($pargroup); 798 1484 while (my @clonedata = $sth2->fetchrow_array) { 799 $sth->execute(@clonedata); 1485 $sthf->execute(@clonedata); 1486 } 1487 # And now the reverse records 1488 $sth2 = $dbh->prepare("SELECT group_id,host,type,val,ttl FROM default_rev_records WHERE group_id=?"); 1489 $sth2->execute($pargroup); 1490 while (my @clonedata = $sth2->fetchrow_array) { 1491 $sthr->execute(@clonedata); 800 1492 } 801 1493 } else { … … 803 1495 # reasonable basic defaults for SOA, MX, NS, and minimal hosting 804 1496 # could load from a config file, but somewhere along the line we need hardcoded bits. 805 $sth->execute('ns1.example.com:hostmaster.example.com', 6, '10800:3600:604800:10800', 0, 0, 0, 86400); 806 $sth->execute('DOMAIN', 1, '192.168.4.2', 0, 0, 0, 7200); 807 $sth->execute('DOMAIN', 15, 'mx.example.com', 10, 0, 0, 7200); 808 $sth->execute('DOMAIN', 2, 'ns1.example.com', 0, 0, 0, 7200); 809 $sth->execute('DOMAIN', 2, 'ns2.example.com', 0, 0, 0, 7200); 810 $sth->execute('www.DOMAIN', 5, 'DOMAIN', 0, 0, 0, 7200); 1497 $sthf->execute('ns1.example.com:hostmaster.example.com', 6, '10800:3600:604800:10800', 0, 0, 0, 86400); 1498 $sthf->execute('DOMAIN', 1, '192.168.4.2', 0, 0, 0, 7200); 1499 $sthf->execute('DOMAIN', 15, 'mx.example.com', 10, 0, 0, 7200); 1500 $sthf->execute('DOMAIN', 2, 'ns1.example.com', 0, 0, 0, 7200); 1501 $sthf->execute('DOMAIN', 2, 'ns2.example.com', 0, 0, 0, 7200); 1502 $sthf->execute('www.DOMAIN', 5, 'DOMAIN', 0, 0, 0, 7200); 1503 # reasonable basic defaults for generic reverse zone. Same as initial SQL tabledef. 1504 $sthr->execute('hostmaster.ADMINDOMAIN:ns1.ADMINDOMAIN', 6, '10800:3600:604800:10800', 86400); 1505 $sthr->execute('unused-%r.ADMINDOMAIN', 65283, 'ZONE', 3600); 811 1506 } 812 1507 … … 1222 1917 ## DNSDB::getSOA() 1223 1918 # Return all suitable fields from an SOA record in separate elements of a hash 1224 # Takes a database handle, default/live flag, and group (default) or domain (live)ID1919 # Takes a database handle, default/live flag, domain/reverse flag, and parent ID 1225 1920 sub getSOA { 1226 1921 $errstr = ''; 1227 1922 my $dbh = shift; 1228 1923 my $def = shift; 1924 my $rev = shift; 1229 1925 my $id = shift; 1230 1926 my %ret; 1231 1927 1232 # (ab)use distance and weight columns to store SOA data 1233 1234 my $sql = "SELECT record_id,host,val,ttl,distance from"; 1235 if ($def eq 'def' or $def eq 'y') { 1236 $sql .= " default_records WHERE group_id=? AND type=$reverse_typemap{SOA}"; 1237 } else { 1238 # we're editing a live SOA record; find based on domain 1239 $sql .= " records WHERE domain_id=? AND type=$reverse_typemap{SOA}"; 1240 } 1928 # (ab)use distance and weight columns to store SOA data? can't for default_rev_records... 1929 # - should really attach serial to the zone parent somewhere 1930 1931 my $sql = "SELECT record_id,host,val,ttl from "._rectable($def,$rev). 1932 " WHERE "._recparent($def,$rev)." = ? AND type=$reverse_typemap{SOA}"; 1933 1241 1934 my $sth = $dbh->prepare($sql); 1242 1935 $sth->execute($id); 1243 1244 my ($recid,$host,$val,$ttl,$serial) = $sth->fetchrow_array() or return; 1936 ##fixme: stick a flag somewhere if the record doesn't exist. by the API, this is an impossible case, but... 1937 1938 my ($recid,$host,$val,$ttl) = $sth->fetchrow_array() or return; 1245 1939 my ($contact,$prins) = split /:/, $host; 1246 1940 my ($refresh,$retry,$expire,$minttl) = split /:/, $val; … … 1248 1942 $ret{recid} = $recid; 1249 1943 $ret{ttl} = $ttl; 1250 $ret{serial} = $serial; 1944 # $ret{serial} = $serial; # ca't use distance for serial with default_rev_records 1251 1945 $ret{prins} = $prins; 1252 1946 $ret{contact} = $contact; … … 1260 1954 1261 1955 1956 ## DNSDB::updateSOA() 1957 # Update the specified SOA record 1958 # Takes a database handle, default/live flag, forward/reverse flag, and SOA data hash 1959 sub updateSOA { 1960 my $dbh = shift; 1961 my $defrec = shift; 1962 my $revrec = shift; 1963 1964 my %soa = @_; 1965 1966 ##fixme: data validation: make sure {recid} is really the SOA for {parent} 1967 my $sql = "UPDATE "._rectable($defrec, $revrec)." SET host=?, val=?, ttl=? WHERE record_id=? AND type=6"; 1968 $dbh->do($sql, undef, ("$soa{contact}:$soa{prins}", "$soa{refresh}:$soa{retry}:$soa{expire}:$soa{minttl}", 1969 $soa{ttl}, $soa{recid})); 1970 1971 } # end updateSOA() 1972 1973 1262 1974 ## DNSDB::getRecLine() 1263 1975 # Return all data fields for a zone record in separate elements of a hash 1264 # Takes a database handle, default/live flag, and record ID1976 # Takes a database handle, default/live flag, forward/reverse flag, and record ID 1265 1977 sub getRecLine { 1266 1978 $errstr = ''; 1267 1979 my $dbh = shift; 1268 my $def = shift; 1980 my $defrec = shift; 1981 my $revrec = shift; 1269 1982 my $id = shift; 1270 1983 1271 my $sql = "SELECT record_id,host,type,val, distance,weight,port,ttl".1272 (($def eq 'def' or $def eq 'y') ? ',group_id FROM default_' : ',domain_id FROM ').1273 "recordsWHERE record_id=?";1984 my $sql = "SELECT record_id,host,type,val,ttl".($revrec eq 'n' ? ',distance,weight,port' : ''). 1985 (($defrec eq 'y') ? ',group_id FROM ' : ',domain_id,rdns_id FROM '). 1986 _rectable($defrec,$revrec)." WHERE record_id=?"; 1274 1987 my $ret = $dbh->selectrow_hashref($sql, undef, ($id) ); 1275 1988 … … 1284 1997 } 1285 1998 1286 $ret->{parid} = (($def eq 'def' or $def eq 'y') ? $ret->{group_id} : $ret->{domain_id}); 1999 # explicitly set a parent id 2000 if ($defrec eq 'y') { 2001 $ret->{parid} = $ret->{group_id}; 2002 } else { 2003 $ret->{parid} = (($revrec eq 'n') ? $ret->{domain_id} : $ret->{rdns_id}); 2004 # and a secondary if we have a custom type that lives in both a forward and reverse zone 2005 $ret->{secid} = (($revrec eq 'y') ? $ret->{domain_id} : $ret->{rdns_id}) if $ret->{type} > 65279; 2006 } 1287 2007 1288 2008 return $ret; … … 1299 2019 $errstr = ''; 1300 2020 my $dbh = shift; 1301 my $type = shift; 2021 my $def = shift; 2022 my $rev = shift; 1302 2023 my $id = shift; 1303 2024 my $nrecs = shift || 'all'; … … 1310 2031 my $filter = shift || ''; 1311 2032 1312 $type = 'y' if $type eq 'def'; 1313 1314 my $sql = "SELECT r.record_id,r.host,r.type,r.val,r.distance,r.weight,r.port,r.ttl FROM "; 1315 $sql .= "default_" if $type eq 'y'; 1316 $sql .= "records r "; 2033 my $sql = "SELECT r.record_id,r.host,r.type,r.val,r.ttl"; 2034 $sql .= ",r.distance,r.weight,r.port" if $rev eq 'n'; 2035 $sql .= " FROM "._rectable($def,$rev)." r "; 1317 2036 1318 2037 # whee! multisort means just passing comma-separated fields in sortby! … … 1326 2045 1327 2046 $sql .= "INNER JOIN rectypes t ON r.type=t.val "; # for sorting by type alphabetically 1328 if ($type eq 'y') { 1329 $sql .= "WHERE r.group_id=?"; 1330 } else { 1331 $sql .= "WHERE r.domain_id=?"; 1332 } 2047 $sql .= "WHERE "._recparent($def,$rev)." = ?"; 1333 2048 $sql .= " AND NOT r.type=$reverse_typemap{SOA}"; 1334 2049 $sql .= " AND host ~* ?" if $filter; 1335 2050 # use alphaorder column for "correct" ordering of sort-by-type instead of DNS RR type number 1336 2051 $sql .= " ORDER BY $newsort $direction"; 1337 $sql .= " LIMIT $nrecs OFFSET ".($nstart*$nrecs) if $nstart ne 'all';1338 2052 1339 2053 my @bindvars = ($id); 1340 2054 push @bindvars, $filter if $filter; 2055 2056 # just to be ultraparanoid about SQL injection vectors 2057 if ($nstart ne 'all') { 2058 $sql .= " LIMIT ? OFFSET ?"; 2059 push @bindvars, $nrecs; 2060 push @bindvars, ($nstart*$nrecs); 2061 } 1341 2062 my $sth = $dbh->prepare($sql) or warn $dbh->errstr; 1342 2063 $sth->execute(@bindvars) or warn "$sql: ".$sth->errstr; … … 1353 2074 1354 2075 ## DNSDB::getRecCount() 1355 # Return count of non-SOA records in domain (or default records in a group) 1356 # Takes a database handle, default/live flag, group/domain ID, and optional filtering modifier 2076 # Return count of non-SOA records in zone (or default records in a group) 2077 # Takes a database handle, default/live flag, reverse/forward flag, group/domain ID, 2078 # and optional filtering modifier 1357 2079 # Returns the count 1358 2080 sub getRecCount { 1359 2081 my $dbh = shift; 1360 2082 my $defrec = shift; 2083 my $revrec = shift; 1361 2084 my $id = shift; 1362 2085 my $filter = shift || ''; … … 1368 2091 my @bindvars = ($id); 1369 2092 push @bindvars, $filter if $filter; 1370 my ($count) = $dbh->selectrow_array("SELECT count(*) FROM ".1371 ($defrec eq 'y' ? 'default_' : '')."records ".1372 "WHERE ".($defrec eq 'y' ? 'group' : 'domain')."_id=? ".1373 1374 ($filter ? " AND host ~* ?" : '') ,1375 2093 my $sql = "SELECT count(*) FROM ". 2094 _rectable($defrec,$revrec). 2095 " WHERE "._recparent($defrec,$revrec)."=? ". 2096 "AND NOT type=$reverse_typemap{SOA}". 2097 ($filter ? " AND host ~* ?" : ''); 2098 my ($count) = $dbh->selectrow_array($sql, undef, (@bindvars) ); 1376 2099 1377 2100 return $count; … … 1387 2110 # and weight/port for SRV 1388 2111 # Returns a status code and detail message in case of error 2112 ##fixme: pass a hash with the record data, not a series of separate values 1389 2113 sub addRec { 1390 2114 $errstr = ''; 1391 2115 my $dbh = shift; 1392 2116 my $defrec = shift; 1393 my $id = shift; 2117 my $revrec = shift; 2118 my $id = shift; # parent (group_id for defrecs, rdns_id for reverse records, 2119 # domain_id for domain records) 1394 2120 1395 2121 my $host = shift; 1396 my $rectype = shift; 2122 my $rectype = shift; # reference so we can coerce it if "+"-types can't find both zones 1397 2123 my $val = shift; 1398 2124 my $ttl = shift; … … 1418 2144 } 1419 2145 2146 my $domid = 0; 2147 my $revid = 0; 2148 2149 my $retcode = 'OK'; # assume everything will go OK 2150 my $retmsg = ''; 2151 2152 # do simple validation first 1420 2153 return ('FAIL', "TTL must be numeric") unless $ttl =~ /^\d+$/; 1421 2154 1422 my $fields = ($defrec eq 'y' ? 'group_id' : 'domain_id').",host,type,val,ttl"; 1423 my $vallen = "?,?,?,?,?"; 1424 my @vallist = ($id,$host,$rectype,$val,$ttl); 1425 1426 my $dist; 1427 if ($rectype == $reverse_typemap{MX} or $rectype == $reverse_typemap{SRV}) { 1428 $dist = shift; 1429 return ('FAIL',"Distance is required for $typemap{$rectype} records") unless defined($dist); 1430 $dist =~ s/\s*//g; 1431 return ('FAIL',"Distance is required, and must be numeric") unless $dist =~ /^\d+$/; 1432 $fields .= ",distance"; 1433 $vallen .= ",?"; 1434 push @vallist, $dist; 1435 } 1436 my $weight; 1437 my $port; 1438 if ($rectype == $reverse_typemap{SRV}) { 1439 # check for _service._protocol. NB: RFC2782 does not say "MUST"... nor "SHOULD"... 1440 # it just says (paraphrased) "... is prepended with _ to prevent DNS collisions" 1441 return ('FAIL',"SRV records must begin with _service._protocol [$host]") 1442 unless $host =~ /^_[A-Za-z]+\._[A-Za-z]+\.[a-zA-Z0-9-]+/; 1443 $weight = shift; 1444 $port = shift; 1445 return ('FAIL',"Port and weight are required for SRV records") unless defined($weight) && defined($port); 1446 $weight =~ s/\s*//g; 1447 $port =~ s/\s*//g; 1448 return ('FAIL',"Port and weight are required, and must be numeric") 1449 unless $weight =~ /^\d+$/ && $port =~ /^\d+$/; 1450 $fields .= ",weight,port"; 1451 $vallen .= ",?,?"; 1452 push @vallist, ($weight,$port); 1453 } 2155 # Quick check on hostname parts. Note the regex is more forgiving than the error message; 2156 # domain names technically are case-insensitive, and we use printf-like % codes for a couple 2157 # of types. Other things may also be added to validate default records of several flavours. 2158 return ('FAIL', "Hostnames may not contain anything other than (0-9 a-z . _)") 2159 if $defrec eq 'n' && $$host !~ /^[0-9a-z_%.]+$/i; 2160 2161 # Collect these even if we're only doing a simple A record so we can call *any* validation sub 2162 my $dist = shift; 2163 my $port = shift; 2164 my $weight = shift; 2165 2166 my $fields; 2167 my @vallist; 2168 2169 # Call the validation sub for the type requested. 2170 ($retcode,$retmsg) = $validators{$$rectype}($dbh, (defrec => $defrec, revrec => $revrec, id => $id, 2171 host => $host, rectype => $rectype, val => $val, addr => $addr, 2172 dist => \$dist, port => \$port, weight => \$weight, 2173 fields => \$fields, vallist => \@vallist) ); 2174 2175 return ($retcode,$retmsg) if $retcode eq 'FAIL'; 2176 2177 # Set up database fields and bind parameters 2178 $fields .= "host,type,val,ttl,"._recparent($defrec,$revrec); 2179 push @vallist, ($$host,$$rectype,$$val,$ttl,$id); 2180 my $vallen = '?'.(',?'x$#vallist); 1454 2181 1455 2182 # Allow transactions, and raise an exception on errors so we can catch it later. … … 1459 2186 1460 2187 eval { 1461 $dbh->do("INSERT INTO ". ($defrec eq 'y' ? 'default_' : '')."records($fields) VALUES ($vallen)",2188 $dbh->do("INSERT INTO "._rectable($defrec, $revrec)." ($fields) VALUES ($vallen)", 1462 2189 undef, @vallist); 1463 2190 $dbh->commit; … … 1469 2196 } 1470 2197 1471 return ( 'OK','OK');2198 return ($retcode, $retmsg); 1472 2199 1473 2200 } # end addRec() … … 1565 2292 my $dbh = shift; 1566 2293 my $defrec = shift; 2294 my $revrec = shift; 1567 2295 my $id = shift; 1568 2296 1569 my $sth = $dbh->prepare("DELETE FROM ". ($defrec eq 'y' ? 'default_' : '')."recordsWHERE record_id=?");2297 my $sth = $dbh->prepare("DELETE FROM "._rectable($defrec,$revrec)." WHERE record_id=?"); 1570 2298 $sth->execute($id); 1571 2299 … … 1577 2305 1578 2306 # Reference hashes. 1579 2307 my %par_tbl = ( 1580 2308 group => 'groups', 1581 2309 user => 'users', 1582 2310 defrec => 'default_records', 2311 defrevrec => 'default_rev_records', 1583 2312 domain => 'domains', 2313 revzone => 'revzones', 1584 2314 record => 'records' 1585 2315 ); 1586 2316 my %id_col = ( 1587 2317 group => 'group_id', 1588 2318 user => 'user_id', 1589 2319 defrec => 'record_id', 2320 defrevrec => 'record_id', 1590 2321 domain => 'domain_id', 2322 revzone => 'rdns_id', 1591 2323 record => 'record_id' 1592 2324 ); 1593 2325 my %par_col = ( 1594 2326 group => 'parent_group_id', 1595 2327 user => 'group_id', 1596 2328 defrec => 'group_id', 2329 defrevrec => 'group_id', 1597 2330 domain => 'group_id', 2331 revzone => 'group_id', 1598 2332 record => 'domain_id' 1599 2333 ); 1600 2334 my %par_type = ( 1601 2335 group => 'group', 1602 2336 user => 'group', 1603 2337 defrec => 'group', 2338 defrevrec => 'group', 1604 2339 domain => 'group', 2340 revzone => 'group', 1605 2341 record => 'domain' 1606 2342 ); 1607 2343 1608 ## DNSDB::getParents() 1609 # Find out which entities are parent to the requested id 1610 # Returns arrayref containing hash pairs of id/type 1611 sub getParents { 1612 my $dbh = shift; 1613 my $id = shift; 1614 my $type = shift; 1615 my $depth = shift || 'all'; # valid values: 'all', 'immed', <int> (stop at this group ID) 1616 1617 my @parlist; 1618 1619 while (1) { 1620 my $result = $dbh->selectrow_hashref("SELECT $par_col{$type} FROM $par_tbl{$type} WHERE $id_col{$type} = ?", 1621 undef, ($id) ); 1622 my %tmp = ($result->{$par_col{$type}} => $par_type{$type}); 1623 unshift @parlist, \%tmp; 1624 last if $result->{$par_col{$type}} == 1; # group 1 is its own parent 1625 $id = $result->{$par_col{$type}}; 1626 $type = $par_type{$type}; 1627 } 1628 1629 return \@parlist; 1630 1631 } # end getParents() 2344 2345 ## DNSDB::getTypelist() 2346 # Get a list of record types for various UI dropdowns 2347 # Takes database handle, forward/reverse/lookup flag, and optional "tag as selected" indicator (defaults to A) 2348 # Returns an arrayref to list of hashrefs perfect for HTML::Template 2349 sub getTypelist { 2350 my $dbh = shift; 2351 my $recgroup = shift; 2352 my $type = shift || $reverse_typemap{A}; 2353 2354 # also accepting $webvar{revrec}! 2355 $recgroup = 'f' if $recgroup eq 'n'; 2356 $recgroup = 'r' if $recgroup eq 'y'; 2357 2358 my $sql = "SELECT val,name FROM rectypes WHERE "; 2359 if ($recgroup eq 'r') { 2360 # reverse zone types 2361 $sql .= "stdflag=2 OR stdflag=3"; 2362 } elsif ($recgroup eq 'l') { 2363 # DNS lookup types. Note we avoid our custom types >= 65280, since those are entirely internal. 2364 $sql .= "(stdflag=1 OR stdflag=2 OR stdflag=3) AND val < 65280"; 2365 } else { 2366 # default; forward zone types. technically $type eq 'f' but not worth the error message. 2367 $sql .= "stdflag=1 OR stdflag=2"; 2368 } 2369 $sql .= " ORDER BY listorder"; 2370 2371 my $sth = $dbh->prepare($sql); 2372 $sth->execute; 2373 my @typelist; 2374 while (my ($rval,$rname) = $sth->fetchrow_array()) { 2375 my %row = ( recval => $rval, recname => $rname ); 2376 $row{tselect} = 1 if $rval == $type; 2377 push @typelist, \%row; 2378 } 2379 2380 # Add SOA on lookups since it's not listed in other dropdowns. 2381 if ($recgroup eq 'l') { 2382 my %row = ( recval => $reverse_typemap{SOA}, recname => 'SOA' ); 2383 $row{tselect} = 1 if $reverse_typemap{SOA} == $type; 2384 push @typelist, \%row; 2385 } 2386 2387 return \@typelist; 2388 } # end getTypelist() 2389 2390 2391 ## DNSDB::parentID() 2392 # Get ID of entity that is nearest parent to requested id 2393 # Takes a database handle and a hash of entity ID, entity type, optional parent type flag 2394 # (domain/reverse zone or group), and optional default/live and forward/reverse flags 2395 # Returns the ID or undef on failure 2396 sub parentID { 2397 my $dbh = shift; 2398 2399 my %args = @_; 2400 2401 # clean up the parent-type. Set it to group if not set; coerce revzone to domain for simpler logic 2402 $args{partype} = 'group' if !$args{partype}; 2403 $args{partype} = 'domain' if $args{partype} eq 'revzone'; 2404 2405 # clean up defrec and revrec. default to live record, forward zone 2406 $args{defrec} = 'n' if !$args{defrec}; 2407 $args{revrec} = 'n' if !$args{revrec}; 2408 2409 if ($par_type{$args{partype}} eq 'domain') { 2410 # only live records can have a domain/zone parent 2411 return unless ($args{type} eq 'record' && $args{defrec} eq 'n'); 2412 my $result = $dbh->selectrow_hashref("SELECT ".($args{revrec} eq 'n' ? 'domain_id' : 'rdns_id'). 2413 " FROM records WHERE record_id = ?", 2414 undef, ($args{id}) ) or return; 2415 return $result; 2416 } else { 2417 # snag some arguments that will either fall through or be overwritten to save some code duplication 2418 my $tmpid = $args{id}; 2419 my $type = $args{type}; 2420 if ($type eq 'record' && $args{defrec} eq 'n') { 2421 # Live records go through the records table first. 2422 ($tmpid) = $dbh->selectrow_array("SELECT ".($args{revrec} eq 'n' ? 'domain_id' : 'rdns_id'). 2423 " FROM records WHERE record_id = ?", 2424 undef, ($args{id}) ) or return; 2425 $type = ($args{revrec} eq 'n' ? 'domain' : 'revzone'); 2426 } 2427 my ($result) = $dbh->selectrow_array("SELECT $par_col{$type} FROM $par_tbl{$type} WHERE $id_col{$type} = ?", 2428 undef, ($tmpid) ); 2429 return $result; 2430 } 2431 # should be impossible to get here with even remotely sane arguments 2432 return; 2433 } # end parentID() 1632 2434 1633 2435 … … 1643 2445 1644 2446 # Return false on invalid types 1645 return 0 if !grep /^$type1$/, ('record','defrec',' user','domain','group');1646 return 0 if !grep /^$type2$/, ('record','defrec',' user','domain','group');2447 return 0 if !grep /^$type1$/, ('record','defrec','defrevrec','user','domain','revzone','group'); 2448 return 0 if !grep /^$type2$/, ('record','defrec','defrevrec','user','domain','revzone','group'); 1647 2449 1648 2450 # Return false on impossible relations 1649 2451 return 0 if $type1 eq 'record'; # nothing may be a child of a record 1650 2452 return 0 if $type1 eq 'defrec'; # nothing may be a child of a record 2453 return 0 if $type1 eq 'defrevrec'; # nothing may be a child of a record 1651 2454 return 0 if $type1 eq 'user'; # nothing may be child of a user 1652 2455 return 0 if $type1 eq 'domain' && $type2 ne 'record'; # domain may not be a parent of anything other than a record 2456 return 0 if $type1 eq 'revzone' && $type2 ne 'record';# reverse zone may not be a parent of anything other than a record 1653 2457 1654 2458 # ennnhhhh.... if we're passed an id of 0, it will never be found. usual 1655 2459 # case would be the UI creating a new <thing>, and so we don't have an ID for 1656 2460 # <thing> to look up yet. in that case the UI should check the parent as well. 1657 # argument for returning 1 is1658 2461 return 0 if $id1 == 0; # nothing can have a parent id of 0 1659 2462 return 1 if $id2 == 0; # anything could have a child id of 0 (or "unknown") … … 1665 2468 return 1 if $type1 eq 'group' && $type2 eq 'group' && $id1 == $id2; 1666 2469 1667 # almost the same loop as getParents() above1668 2470 my $id = $id2; 1669 2471 my $type = $type2; 1670 2472 my $foundparent = 0; 1671 2473 2474 # Records are the only entity with two possible parents. We need to split the parent checks on 2475 # domain/rdns. 2476 if ($type eq 'record') { 2477 my ($dom,$rdns) = $dbh->selectrow_array("SELECT domain_id,rdns_id FROM records WHERE record_id=?", 2478 undef, ($id)); 2479 # check immediate parent against request 2480 return 1 if $type1 eq 'domain' && $id1 == $dom; 2481 return 1 if $type1 eq 'revzone' && $id1 == $rdns; 2482 # if request is group, check *both* parents. Only check if the parent is nonzero though. 2483 return 1 if $dom && isParent($dbh, $id1, $type1, $dom, 'domain'); 2484 return 1 if $rdns && isParent($dbh, $id1, $type1, $rdns, 'revzone'); 2485 # exit here since we've executed the loop below by proxy in the above recursive calls. 2486 return 0; 2487 } 2488 2489 # almost the same loop as getParents() above 1672 2490 my $limiter = 0; 1673 2491 while (1) { … … 1677 2495 if (!$result) { 1678 2496 $limiter++; 1679 ##fixme: how often will this happen on a live site? 2497 ##fixme: how often will this happen on a live site? fail at max limiter <n>? 1680 2498 warn "no results looking for $sql with id $id (depth $limiter)\n"; 1681 2499 last; … … 1686 2504 } else { 1687 2505 ##fixme: do we care about trying to return a "no such record/domain/user/group" error? 2506 # should be impossible to create an inconsistent DB just with API calls. 1688 2507 warn $dbh->errstr." $sql, $id" if $dbh->errstr; 1689 2508 } -
branches/stable/dns.cgi
r438 r544 62 62 63 63 # shut up some warnings, in case we arrive somewhere we forgot to set this 64 $webvar{defrec} = 'n' if !$webvar{defrec}; 64 $webvar{defrec} = 'n' if !$webvar{defrec}; # non-default records 65 $webvar{revrec} = 'n' if !$webvar{revrec}; # non-reverse (domain) records 65 66 66 67 # load some local system defaults (mainly DB connect info) … … 87 88 $session->param('domlistsortby','domain'); 88 89 $session->param('domlistorder','ASC'); 90 $session->param('revzonessortby','revnet'); 91 $session->param('revzonesorder','ASC'); 89 92 $session->param('useradminsortby','user'); 90 93 $session->param('useradminorder','ASC'); … … 204 207 my $page; 205 208 eval { 206 $page = HTML::Template->new(filename => "$templatedir/$webvar{page}.tmpl"); 209 # sigh. can't set loop_context_vars or global_vars once instantiated. 210 $page = HTML::Template->new(filename => "$templatedir/$webvar{page}.tmpl", 211 loop_context_vars => 1, global_vars => 1); 207 212 }; 208 213 if ($@) { 209 warn "Bad page $webvar{page} requested";214 my $msg = $@; 210 215 $page = HTML::Template->new(filename => "$templatedir/badpage.tmpl"); 211 $page->param(badpage => $q->escapeHTML($webvar{page})); 216 if (-e "$templatedir/$webvar{page}.tmpl") { 217 $page->param(badtemplate => $q->escapeHTML($msg)); 218 } else { 219 warn "Bad page $webvar{page} requested"; 220 $page->param(badpage => $q->escapeHTML($webvar{page})); 221 } 212 222 $webvar{page} = 'badpage'; 213 223 } … … 279 289 280 290 # I hate special cases. 291 ##fixme: probably need to handle webvar{revrec}=='y' too 281 292 if ($webvar{page} eq 'reclist' && $webvar{defrec} eq 'y') { 282 my %args = (page => $webvar{page}, id => $curgroup, defrec => $webvar{defrec} );293 my %args = (page => $webvar{page}, id => $curgroup, defrec => $webvar{defrec}, revrec => $webvar{revrec}); 283 294 $args{errmsg} = $errmsg if $errmsg; 284 295 changepage(%args); … … 300 311 301 312 } elsif ($webvar{page} eq 'domlist' or $webvar{page} eq 'index') { 313 314 $page->param(domlist => 1); 302 315 303 316 # hmm. seeing problems in some possibly-not-so-corner cases. … … 312 325 my $stat = domStatus($dbh,$webvar{id},$webvar{domstatus}); 313 326 ##fixme switch to more consise "Enabled <domain"/"Disabled <domain>" as with users? 314 logaction($webvar{id}, $session->param("username"), parentID($webvar{id}, 'dom', 'group'), 327 logaction($webvar{id}, $session->param("username"), 328 parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec})), 315 329 "Changed ".domainName($dbh, $webvar{id})." state to ".($stat ? 'active' : 'inactive')); 316 330 $page->param(resultmsg => "Changed ".domainName($dbh, $webvar{id})." state to ". … … 366 380 367 381 my ($code,$msg) = addDomain($dbh,$webvar{domain},$webvar{group},($webvar{makeactive} eq 'on' ? 1 : 0), 368 ( name => $session->param("username"), id => $session->param("uid")));382 (username => $session->param("username"), id => $session->param("uid"))); 369 383 370 384 if ($code eq 'OK') { … … 401 415 402 416 } elsif ($webvar{del} eq 'ok') { 403 my $pargroup = parentID($ webvar{id}, 'dom', 'group');417 my $pargroup = parentID($dbh, (id => $webvar{id}, type => 'domain', revrec => $webvar{revrec})); 404 418 my $dom = domainName($dbh, $webvar{id}); 405 419 my ($code,$msg) = delDomain($dbh, $webvar{id}); … … 418 432 } 419 433 434 } elsif ($webvar{page} eq 'revzones') { 435 436 $webvar{revrec} = 'y'; 437 $page->param(curpage => $webvar{page}); 438 listzones(); 439 440 } elsif ($webvar{page} eq 'newrevzone') { 441 442 ## scope/access check - use domain settings? invent new (bleh) 443 changepage(page => "revzones", errmsg => "You are not permitted to add reverse zones") 444 unless ($permissions{admin} || $permissions{domain_create}); 445 446 fill_grouplist("grouplist"); 447 448 if ($webvar{add_failed}) { 449 $page->param(add_failed => 1); 450 $page->param(errmsg => $webvar{errmsg}); 451 $page->param(revzone => $webvar{revzone}); 452 $page->param(revpatt => $webvar{revpatt}); 453 } 454 455 } elsif ($webvar{page} eq 'addrevzone') { 456 457 changepage(page => "revzones", errmsg => "You are not permitted to add reverse zones") 458 unless ($permissions{admin} || $permissions{domain_create}); 459 460 # security check - does the user have permission to access this entity? 461 if (!check_scope(id => $webvar{group}, type => 'group')) { 462 changepage(page => "newrevzone", add_failed => 1, revzone => $webvar{revzone}, revpatt => $webvar{revpatt}, 463 errmsg => "You do not have permission to add a reverse zone to the requested group"); 464 } 465 466 my ($code,$msg) = addRDNS($dbh, $webvar{revzone}, $webvar{revpatt}, $webvar{group}, 467 ($webvar{makeactive} eq 'on' ? 1 : 0), 468 (username => $session->param("username"), id => $session->param("uid")) ); 469 470 if ($code eq 'OK') { 471 logaction(0, $session->param("username"), $webvar{group}, "Added reverse zone $webvar{revzone}", $msg); 472 changepage(page => "reclist", id => $msg, revrec => 'y'); 473 } else { 474 logaction(0, $session->param("username"), $webvar{group}, "Failed adding reverse zone $webvar{revzone} ($msg)"); 475 changepage(page => "newrevzone", add_failed => 1, revzone => $webvar{revzone}, revpatt => $webvar{revpatt}, 476 errmsg => $msg); 477 } 478 479 #} elsif ($webvar{page} eq 'delrevzone') { 480 420 481 } elsif ($webvar{page} eq 'reclist') { 421 482 422 483 # security check - does the user have permission to view this entity? 423 if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) { 484 if (!check_scope(id => $webvar{id}, type => 485 ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) { 424 486 $page->param(errmsg => "You are not permitted to view or change the requested ". 425 ($webvar{defrec} eq 'y' ? "group's default records" : "domain's records")); 487 ($webvar{defrec} eq 'y' ? "group's default records" : 488 ($webvar{revrec} eq 'y' ? "reverse zone's records" : "domain's records"))); 426 489 $page->param(perm_err => 1); # this causes the template to skip the record listing output. 427 490 goto DONERECLIST; # and now we skip filling in the content which is not printed due to perm_err above … … 446 509 447 510 $page->param(defrec => $webvar{defrec}); 511 $page->param(revrec => $webvar{revrec}); 448 512 $page->param(id => $webvar{id}); 449 513 $page->param(curpage => $webvar{page}); 450 514 451 my $count = getRecCount($dbh, $webvar{defrec}, $webvar{ id}, $filter);515 my $count = getRecCount($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}, $filter); 452 516 453 517 $sortby = 'host'; … … 460 524 461 525 # set up the headers 462 my @cols = ('host', 'type', 'val', 'distance', 'weight', 'port', 'ttl'); 463 my %colheads = (host => 'Name', type => 'Type', val => 'Address', 526 my @cols; 527 my %colheads; 528 if ($webvar{revrec} eq 'n') { 529 @cols = ('host', 'type', 'val', 'distance', 'weight', 'port', 'ttl'); 530 %colheads = (host => 'Name', type => 'Type', val => 'Address', 464 531 distance => 'Distance', weight => 'Weight', port => 'Port', ttl => 'TTL'); 465 my %custom = (id => $webvar{id}, defrec => $webvar{defrec}); 532 } else { 533 @cols = ('host', 'type', 'val', 'ttl'); 534 %colheads = (host => 'IP Address', type => 'Type', val => 'Hostname', ttl => 'TTL'); 535 } 536 my %custom = (id => $webvar{id}, defrec => $webvar{defrec}, revrec => $webvar{revrec}); 466 537 fill_colheads($sortby, $sortorder, \@cols, \%colheads, \%custom); 467 538 468 539 # fill the page-count and first-previous-next-last-all details 469 540 fill_pgcount($count,"records", 470 ($webvar{defrec} eq 'y' ? "group ".groupName($dbh,$webvar{id}) : domainName($dbh,$webvar{id}))); 541 ($webvar{defrec} eq 'y' ? "group ".groupName($dbh,$webvar{id}) : 542 ($webvar{revrec} eq 'y' ? revName($dbh,$webvar{id}) : domainName($dbh,$webvar{id})) 543 )); 471 544 fill_fpnla($count); # should put some params on this sub... 472 545 473 546 $page->param(defrec => $webvar{defrec}); 474 if ($webvar{defrec} eq 'y') { 475 showdomain('y',$curgroup); 476 } else { 477 showdomain('n',$webvar{id}); 547 showzone($webvar{defrec}, $webvar{revrec}, $webvar{id}); 548 if ($webvar{defrec} eq 'n') { 549 # showzone('n',$webvar{id}); 478 550 ##fixme: permission for viewing logs? 479 $page->param(logdom => 1); 551 ##fixme: determine which slice of the log we view (group, domain, revzone) 552 if ($webvar{revrec} eq 'n') { 553 $page->param(logdom => 1); 554 } else { 555 $page->param(logrdns => 1); 556 } 480 557 } 481 558 … … 484 561 $session->clear('resultmsg'); 485 562 } 563 if ($session->param('warnmsg')) { 564 $page->param(warnmsg => $session->param('warnmsg')); 565 $session->clear('warnmsg'); 566 } 486 567 if ($session->param('errmsg')) { 487 568 $page->param(errmsg => $session->param('errmsg')); … … 497 578 498 579 # security check - does the user have permission to access this entity? 499 if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'defrec' : 'record'))) { 580 if (!check_scope(id => $webvar{id}, type => 581 ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) { 500 582 $page->param(perm_err => "You are not permitted to edit the requested record"); 501 583 goto DONEREC; 502 584 } 503 585 # round 2, check the parent. 504 if (!check_scope(id => $webvar{parentid}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) { 586 if (!check_scope(id => $webvar{parentid}, type => 587 ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) { 505 588 my $msg = ($webvar{defrec} eq 'y' ? 506 589 "You are not permitted to add or edit default records in the requested group" : 507 "You are not permitted to add or edit records in the requested domain ");590 "You are not permitted to add or edit records in the requested domain/zone"); 508 591 $page->param(perm_err => $msg); 509 592 goto DONEREC; 510 593 } 594 595 $page->param(defrec => $webvar{defrec}); 596 $page->param(revrec => $webvar{revrec}); 597 $page->param(fwdzone => $webvar{revrec} eq 'n'); 511 598 512 599 if ($webvar{recact} eq 'new') { … … 518 605 $page->param(recact => "add"); 519 606 $page->param(parentid => $webvar{parentid}); 520 $page->param(defrec => $webvar{defrec});521 607 522 608 fill_recdata(); … … 527 613 unless ($permissions{admin} || $permissions{record_create}); 528 614 529 ##fixme: this should probably go in DNSDB::addRec(), need to ponder what to do about PTR and friends 530 # prevent out-of-domain records from getting added by appending the domain, or DOMAIN for default records 531 my $pname = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid})); 532 $webvar{name} =~ s/\.*$/\.$pname/ if $webvar{name} !~ /$pname$/; 533 534 my @recargs = ($dbh,$webvar{defrec},$webvar{parentid},$webvar{name},$webvar{type},$webvar{address},$webvar{ttl}); 615 my @recargs = ($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid}, 616 \$webvar{name},\$webvar{type},\$webvar{address},$webvar{ttl}); 535 617 if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}) { 536 618 push @recargs, $webvar{distance}; … … 543 625 my ($code,$msg) = addRec(@recargs); 544 626 545 if ($code eq 'OK') { 627 if ($code eq 'OK' || $code eq 'WARN') { 628 my $restr; 546 629 if ($webvar{defrec} eq 'y') { 547 my$restr = "Added default record '$webvar{name} $typemap{$webvar{type}}";630 $restr = "Added default record '$webvar{name} $typemap{$webvar{type}}"; 548 631 $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX'; 549 632 $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]" … … 551 634 $restr .= " $webvar{address}', TTL $webvar{ttl}"; 552 635 logaction(0, $session->param("username"), $webvar{parentid}, $restr); 553 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr);554 636 } else { 555 my$restr = "Added record '$webvar{name} $typemap{$webvar{type}}";637 $restr = "Added record '$webvar{name} $typemap{$webvar{type}}"; 556 638 $restr .= " [distance $webvar{distance}]" if $typemap{$webvar{type}} eq 'MX'; 557 639 $restr .= " [priority $webvar{distance}] [weight $webvar{weight}] [port $webvar{port}]" 558 640 if $typemap{$webvar{type}} eq 'SRV'; 559 641 $restr .= " $webvar{address}', TTL $webvar{ttl}"; 560 logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'), $restr); 561 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr); 562 } 642 logaction($webvar{parentid}, $session->param("username"), 643 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})), $restr); 644 } 645 my %pageparams = (page => "reclist", id => $webvar{parentid}, 646 defrec => $webvar{defrec}, revrec => $webvar{revrec}); 647 $pageparams{warnmsg} = $msg."<br><br>\n".$restr if $code eq 'WARN'; 648 $pageparams{resultmsg} = $restr if $code eq 'OK'; 649 changepage(%pageparams); 563 650 } else { 564 651 $page->param(failed => 1); … … 568 655 $page->param(recact => "add"); 569 656 $page->param(parentid => $webvar{parentid}); 570 $page->param(defrec => $webvar{defrec});571 657 $page->param(id => $webvar{id}); 572 658 fill_recdata(); # populate the form... er, mostly. 573 $page->param(name => $webvar{name});574 659 if ($config{log_failures}) { 575 660 if ($webvar{defrec} eq 'y') { … … 577 662 "Failed adding default record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)"); 578 663 } else { 579 logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'), 664 logaction($webvar{parentid}, $session->param("username"), 665 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})), 580 666 "Failed adding record '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl} ($msg)"); 581 667 } … … 592 678 $page->param(parentid => $webvar{parentid}); 593 679 $page->param(id => $webvar{id}); 594 $page->param(defrec => $webvar{defrec}); 595 my $recdata = getRecLine($dbh, $webvar{defrec}, $webvar{id}); 680 my $recdata = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}); 596 681 $page->param(name => $recdata->{host}); 597 682 $page->param(address => $recdata->{val}); … … 600 685 $page->param(port => $recdata->{port}); 601 686 $page->param(ttl => $recdata->{ttl}); 602 fill_rectypes($recdata->{type});687 $page->param(typelist => getTypelist($dbh, $webvar{revrec}, $webvar{type})); 603 688 604 689 } elsif ($webvar{recact} eq 'update') { … … 613 698 614 699 # get current/previous record info so we can log "updated 'foo A 1.2.3.4' to 'foo A 2.3.4.5'" 615 my $oldrec = getRecLine($dbh, $webvar{defrec}, $webvar{ id});700 my $oldrec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}); 616 701 617 702 my ($code,$msg) = updateRec($dbh,$webvar{defrec},$webvar{id}, … … 629 714 my $restr = "Updated record from '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl}\n". 630 715 "to '$webvar{name} $typemap{$webvar{type}} $webvar{address}', TTL $webvar{ttl}"; 631 logaction($webvar{parentid}, $session->param("username"), parentID($webvar{id}, 'rec', 'group'), $restr); 716 logaction($webvar{parentid}, $session->param("username"), 717 parentID($dbh, (id => $webvar{id}, type => 'record', defrec => $webvar{defrec}, 718 revrec => $webvar{revrec}, partype => 'group')), 719 $restr); 632 720 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr); 633 721 } … … 639 727 $page->param(recact => "update"); 640 728 $page->param(parentid => $webvar{parentid}); 641 $page->param(defrec => $webvar{defrec});642 729 $page->param(id => $webvar{id}); 643 730 fill_recdata(); … … 647 734 "Failed updating default record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)"); 648 735 } else { 649 logaction($webvar{parentid}, $session->param("username"), parentID($webvar{parentid}, 'dom', 'group'), 736 logaction($webvar{parentid}, $session->param("username"), 737 parentID($dbh, (id => $webvar{parentid}, type => 'domain', revrec => $webvar{revrec})), 650 738 "Failed updating record '$typemap{$webvar{type}} $webvar{name} $webvar{address}', TTL $webvar{ttl} ($msg)"); 651 739 } … … 658 746 } else { 659 747 $page->param(parentid => $webvar{parentid}); 660 $page->param(dohere => domainName($dbh,$webvar{parentid})); 748 $page->param(dohere => domainName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'n'; 749 $page->param(dohere => revName($dbh,$webvar{parentid})) if $webvar{revrec} eq 'y'; 661 750 } 662 751 … … 668 757 # This is a complete separate segment since it uses a different template from add/edit records above 669 758 670 changepage(page => "reclist", errmsg => "You are not permitted to delete records", id => $webvar{parentid}) 759 changepage(page => "reclist", errmsg => "You are not permitted to delete records", id => $webvar{parentid}, 760 defrec => $webvar{defrec}, revrec => $webvar{revrec}) 671 761 unless ($permissions{admin} || $permissions{record_delete}); 672 762 673 763 if (!check_scope(id => $webvar{id}, type => 674 764 ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) { 675 changepage(page => 'domlist', errmsg => "You do not have permission to delete records in the requested ". 765 # redirect to domlist because we don't have permission for the entity requested 766 changepage(page => 'domlist', revrec => $webvar{revrec}, 767 errmsg => "You do not have permission to delete records in the requested ". 676 768 ($webvar{defrec} eq 'y' ? 'group' : 'domain')); 677 769 } … … 679 771 $page->param(id => $webvar{id}); 680 772 $page->param(defrec => $webvar{defrec}); 773 $page->param(revrec => $webvar{revrec}); 681 774 $page->param(parentid => $webvar{parentid}); 682 775 # first pass = confirm y/n (sorta) 683 776 if (!defined($webvar{del})) { 684 777 $page->param(del_getconf => 1); 685 my $rec = getRecLine($dbh, $webvar{defrec},$webvar{id});778 my $rec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}); 686 779 $page->param(host => $rec->{host}); 687 780 $page->param(ftype => $typemap{$rec->{type}}); … … 689 782 } elsif ($webvar{del} eq 'ok') { 690 783 # get rec data before we try to delete it 691 my $rec = getRecLine($dbh, $webvar{defrec},$webvar{id});692 my ($code,$msg) = delRec($dbh, $webvar{defrec},$webvar{id});784 my $rec = getRecLine($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}); 785 my ($code,$msg) = delRec($dbh, $webvar{defrec}, $webvar{revrec}, $webvar{id}); 693 786 if ($code eq 'OK') { 694 787 if ($webvar{defrec} eq 'y') { 788 my $recclass = ($webvar{revrec} eq 'n' ? 'default record' : 'default reverse record'); 695 789 ##fixme: log distance for MX; log port/weight/distance for SRV 696 my $restr = "Deleted default record'$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}";790 my $restr = "Deleted $recclass '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}"; 697 791 logaction(0, $session->param("username"), $rec->{parid}, $restr); 698 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr); 792 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, 793 revrec => $webvar{revrec}, resultmsg => $restr); 699 794 } else { 700 my $restr = "Deleted record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}"; 701 logaction($rec->{parid}, $session->param("username"), parentID($rec->{parid}, 'dom', 'group'), $restr); 702 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, resultmsg => $restr); 795 my $recclass = ($webvar{revrec} eq 'n' ? 'record' : 'reverse record'); 796 my $restr = "Deleted $recclass '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl}"; 797 logaction($rec->{parid}, $session->param("username"), 798 parentID($dbh, (id => $rec->{parid}, type => 'domain', revrec => $webvar{revrec})), 799 $restr); 800 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, 801 revrec => $webvar{revrec}, resultmsg => $restr); 703 802 } 704 803 } else { … … 710 809 " TTL $rec->{ttl} ($msg)"); 711 810 } else { 712 logaction($rec->{parid}, $session->param("username"), parentID($rec->{parid}, 'dom', 'group'), 811 logaction($rec->{parid}, $session->param("username"), 812 parentID($dbh, (id => $rec->{parid}, type => 'domain', revrec => $webvar{revrec})), 713 813 "Failed deleting record '$rec->{host} $typemap{$rec->{type}} $rec->{val}', TTL $rec->{ttl} ($msg)"); 714 814 } 715 815 } 716 816 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, 717 errmsg => "Error deleting record: $msg");817 revrec => $webvar{revrec}, errmsg => "Error deleting record: $msg"); 718 818 } 719 819 } else { 720 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec} );820 changepage(page => "reclist", id => $webvar{parentid}, defrec => $webvar{defrec}, revrec => $webvar{revrec}); 721 821 } 722 822 … … 724 824 725 825 # security check - does the user have permission to view this entity? 726 if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) { 826 # id is domain/revzone/group id 827 if (!check_scope(id => $webvar{id}, type => 828 ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) { 727 829 changepage(page => 'domlist', errmsg => "You do not have permission to edit the ". 728 830 ($webvar{defrec} eq 'y' ? 'default ' : '')."SOA record for the requested ". … … 744 846 # security check - does the user have permission to view this entity? 745 847 # pass 1, record ID 746 if (!check_scope(id => $webvar{recid}, type => ($webvar{defrec} eq 'y' ? 'defrec' : 'record'))) { 848 if (!check_scope(id => $webvar{recid}, type => 849 ($webvar{defrec} eq 'y' ? ($webvar{revrec} eq 'y' ? 'defrevrec' : 'defrec') : 'record'))) { 747 850 changepage(page => 'domlist', errmsg => "You do not have permission to edit the requested SOA record"); 748 851 } 749 852 # pass 2, parent (group or domain) ID 750 if (!check_scope(id => $webvar{id}, type => ($webvar{defrec} eq 'y' ? 'group' : 'domain'))) { 853 if (!check_scope(id => $webvar{id}, type => 854 ($webvar{defrec} eq 'y' ? 'group' : ($webvar{revrec} eq 'y' ? 'revzone' : 'domain')))) { 751 855 changepage(page => 'domlist', errmsg => "You do not have permission to edit the ". 752 856 ($webvar{defrec} eq 'y' ? 'default ' : '')."SOA record for the requested ". … … 787 891 $logdomain = 0; 788 892 } else { 789 $loggroup = parentID($ logdomain, 'dom', 'group', $webvar{defrec});893 $loggroup = parentID($dbh, (id => $logdomain, type => 'domain', revrec => $webvar{revrec})); 790 894 } 791 895 … … 898 1002 } elsif ($webvar{del} eq 'ok') { 899 1003 my $deleteme = groupName($dbh,$webvar{id}); # get this before we delete it... 900 my $delparent = parentID($ webvar{id}, 'group','group');1004 my $delparent = parentID($dbh, (id => $webvar{id}, type => 'group')); 901 1005 my ($code,$msg) = delGroup($dbh, $webvar{id}); 902 1006 if ($code eq 'OK') { … … 1041 1145 my ($code, $msg) = changeGroup($dbh, 'domain', $webvar{$_}, $webvar{destgroup}); 1042 1146 if ($code eq 'OK') { 1043 logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'), 1147 logaction($webvar{$_}, $session->param("username"), 1148 parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})), 1044 1149 "Moved domain ".domainName($dbh, $webvar{$_})." to group $newgname"); 1045 1150 $row{domok} = ($code eq 'OK'); 1046 1151 } else { 1047 logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'), 1152 logaction($webvar{$_}, $session->param("username"), 1153 parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})), 1048 1154 "Failed to move domain ".domainName($dbh, $webvar{$_})." to group $newgname: $msg") 1049 1155 if $config{log_failures}; … … 1072 1178 ##fixme: error handling on status change 1073 1179 my $stat = domStatus($dbh,$webvar{$_},($webvar{bulkaction} eq 'activate' ? 'domon' : 'domoff')); 1074 logaction($webvar{$_}, $session->param("username"), parentID($webvar{$_}, 'dom', 'group'), 1075 "Changed domain ".domainName($dbh, $webvar{$_})." state to ".($stat ? 'active' : 'inactive')); 1180 logaction($webvar{$_}, $session->param("username"), 1181 parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})), 1182 "Changed domain ".domainName($dbh, $webvar{$_})." state to ".($stat ? 'active' : 'inactive')); 1076 1183 $row{domok} = 1; 1077 1184 # $row{domok} = ($code eq 'OK'); … … 1097 1204 } 1098 1205 $row{domain} = domainName($dbh,$webvar{$_}); 1099 my $pargroup = parentID($ webvar{$_}, 'dom', 'group');1206 my $pargroup = parentID($dbh, (id => $webvar{$_}, type => 'domain', revrec => $webvar{revrec})); 1100 1207 my $dom = domainName($dbh, $webvar{$_}); 1101 1208 my ($code, $msg) = delDomain($dbh, $webvar{$_}); … … 1130 1237 if ($flag && ($permissions{admin} || $permissions{user_edit})) { 1131 1238 my $stat = userStatus($dbh,$webvar{id},$webvar{userstatus}); 1132 logaction(0, $session->param("username"), parentID($ webvar{id}, 'user', 'group'),1239 logaction(0, $session->param("username"), parentID($dbh, (id => $webvar{id}, type => 'user')), 1133 1240 ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u')); 1134 1241 $page->param(resultmsg => ($stat ? 'Enabled' : 'Disabled')." ".userFullName($dbh, $webvar{id}, '%u')); … … 1388 1495 1389 1496 $page->param(qfor => $webvar{qfor}) if $webvar{qfor}; 1390 fill_rectypes($webvar{type} ? $webvar{type} : '', 1);1497 $page->param(typelist => getTypelist($dbh, 'l', ($webvar{type} ? $webvar{type} : undef))); 1391 1498 $page->param(nrecurse => $webvar{nrecurse}) if $webvar{nrecurse}; 1392 1499 $page->param(resolver => $webvar{resolver}) if $webvar{resolver}; … … 1563 1670 } 1564 1671 $page->param(logfor => 'domain '.domainName($dbh,$id)); 1672 } elsif ($webvar{ltype} && $webvar{ltype} eq 'rdns') { 1673 $sql .= "rdns_id=?"; 1674 $id = $webvar{id}; 1675 if (!check_scope(id => $id, type => 'revzone')) { 1676 $page->param(errmsg => "You are not permitted to view log entries for the requested reverse zone"); 1677 goto DONELOG; 1678 } 1679 $page->param(logfor => 'reverse zone '.revName($dbh,$id)); 1565 1680 } else { 1566 1681 # Default to listing curgroup log … … 1570 1685 # group log is always for the "current" group 1571 1686 } 1687 ##fixme: 1688 # - filtering 1689 # - show reverse zone column? 1690 # - pagination/limiting number of records - put newest-first so user 1691 # doesn't always need to go to the last page for recent activity? 1572 1692 my $sth = $dbh->prepare($sql); 1573 1693 $sth->execute($id); … … 1601 1721 $page->param(logingrp => groupName($dbh,$logingroup)); 1602 1722 $page->param(logingrp_num => $logingroup); 1723 1724 ##fixme 1725 $page->param(mayrdns => 1); 1603 1726 1604 1727 $page->param(maydefrec => $permissions{admin}); … … 1696 1819 # handle user check 1697 1820 my $newurl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?sid=$sid"; 1698 foreach ( keys %params) {1821 foreach (sort keys %params) { 1699 1822 $newurl .= "&$_=".$q->url_encode($params{$_}); 1700 1823 } … … 1743 1866 } 1744 1867 1745 sub show domain{1868 sub showzone { 1746 1869 my $def = shift; 1870 my $rev = shift; 1747 1871 my $id = shift; 1748 1872 1749 1873 # get the SOA first 1750 my %soa = getSOA($dbh,$def,$ id);1874 my %soa = getSOA($dbh,$def,$rev,$id); 1751 1875 1752 1876 $page->param(contact => $soa{contact}); … … 1758 1882 $page->param(ttl => $soa{ttl}); 1759 1883 1760 my $foo2 = getDomRecs($dbh,$def,$ id,$perpage,$webvar{offset},$sortby,$sortorder,$filter);1884 my $foo2 = getDomRecs($dbh,$def,$rev,$id,$perpage,$webvar{offset},$sortby,$sortorder,$filter); 1761 1885 1762 1886 my $row = 0; … … 1765 1889 $rec->{row} = $row % 2; 1766 1890 $rec->{defrec} = $def; 1891 $rec->{revrec} = $rev; 1767 1892 $rec->{sid} = $webvar{sid}; 1768 1893 $rec->{id} = $id; 1894 $rec->{fwdzone} = $rev eq 'n'; 1769 1895 $rec->{distance} = 'n/a' unless ($rec->{type} eq 'MX' || $rec->{type} eq 'SRV'); 1770 1896 $rec->{weight} = 'n/a' unless ($rec->{type} eq 'SRV'); … … 1778 1904 } 1779 1905 1780 # fill in record type list on add/update/edit record template1781 sub fill_rectypes {1782 my $type = shift || $reverse_typemap{A};1783 my $soaflag = shift || 0;1784 1785 my $sth = $dbh->prepare("SELECT val,name FROM rectypes WHERE stdflag=1 ORDER BY listorder");1786 $sth->execute;1787 my @typelist;1788 while (my ($rval,$rname) = $sth->fetchrow_array()) {1789 my %row = ( recval => $rval, recname => $rname );1790 $row{tselect} = 1 if $rval == $type;1791 push @typelist, \%row;1792 }1793 if ($soaflag) {1794 my %row = ( recval => $reverse_typemap{SOA}, recname => 'SOA' );1795 $row{tselect} = 1 if $reverse_typemap{SOA} == $type;1796 push @typelist, \%row;1797 }1798 $page->param(typelist => \@typelist);1799 } # fill_rectypes1800 1801 1906 sub fill_recdata { 1802 fill_rectypes($webvar{type});1907 $page->param(typelist => getTypelist($dbh, $webvar{revrec}, $webvar{type})); 1803 1908 1804 1909 # le sigh. we may get called with many empty %webvar keys … … 1807 1912 ##todo: allow BIND-style bare names, ASS-U-ME that the name is within the domain? 1808 1913 # prefill <domain> or DOMAIN in "Host" space for new records 1809 my $domroot = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid})); 1810 $page->param(name => $domroot); 1811 $page->param(address => $webvar{address}); 1812 $page->param(distance => $webvar{distance}) 1914 if ($webvar{revrec} eq 'n') { 1915 my $domroot = ($webvar{defrec} eq 'y' ? 'DOMAIN' : domainName($dbh,$webvar{parentid})); 1916 $page->param(name => $domroot); 1917 $page->param(address => $webvar{address}); 1918 $page->param(distance => $webvar{distance}) 1813 1919 if ($webvar{type} == $reverse_typemap{MX} or $webvar{type} == $reverse_typemap{SRV}); 1814 $page->param(weight => $webvar{weight}) if $webvar{type} == $reverse_typemap{SRV}; 1815 $page->param(port => $webvar{port}) if $webvar{type} == $reverse_typemap{SRV}; 1920 $page->param(weight => $webvar{weight}) if $webvar{type} == $reverse_typemap{SRV}; 1921 $page->param(port => $webvar{port}) if $webvar{type} == $reverse_typemap{SRV}; 1922 } else { 1923 my $domroot = ($webvar{defrec} eq 'y' ? 'ADMINDOMAIN' : ".$config{domain}"); 1924 $page->param(name => ($webvar{name} ? $webvar{name} : $domroot)); 1925 my $zname = ($webvar{defrec} eq 'y' ? 'ZONE' : revName($dbh,$webvar{parentid})); 1926 $zname =~ s|\d*/\d+$||; 1927 $page->param(address => ($webvar{address} ? $webvar{address} : $zname)); 1928 } 1816 1929 # retrieve the right ttl instead of falling (way) back to the hardcoded system default 1817 my %soa = getSOA($dbh,$webvar{defrec},$webvar{ parentid});1930 my %soa = getSOA($dbh,$webvar{defrec},$webvar{revrec},$webvar{parentid}); 1818 1931 $page->param(ttl => ($webvar{ttl} ? $webvar{ttl} : $soa{minttl})); 1819 1932 } … … 1893 2006 # on a page showing nothing. 1894 2007 # For bonus points, this reverts to the original offset on clicking the "All" link (mostly) 1895 if ($offset ne 'all') { 2008 if ($offset ne 'all') { 1896 2009 $offset-- while ($offset * $perpage) >= $pgcount; 1897 2010 } … … 1907 2020 } # end fill_pgcount() 1908 2021 1909 sub listdomains { 1910 2022 2023 sub listdomains { listzones(); } # temp 2024 2025 sub listzones { 1911 2026 # ACLs 1912 2027 $page->param(domain_create => ($permissions{admin} || $permissions{domain_create}) ); … … 1918 2033 my $childlist = join(',',@childgroups); 1919 2034 1920 my $sql = "SELECT count(*) FROM domains WHERE group_id IN ($curgroup".($childlist ? ",$childlist" : '').")". 1921 ($startwith ? " AND domain ~* ?" : ''). 1922 ($filter ? " AND domain ~* ?" : ''); 1923 my $sth = $dbh->prepare($sql); 1924 $sth->execute(@filterargs); 1925 my ($count) = $sth->fetchrow_array; 2035 my $count = getZoneCount($dbh, (childlist => $childlist, curgroup => $curgroup, revrec => $webvar{revrec}, 2036 filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef) ) ); 1926 2037 1927 2038 # fill page count and first-previous-next-last-all bits 1928 fill_pgcount($count, "domains",groupName($dbh,$curgroup));2039 fill_pgcount($count,($webvar{revrec} eq 'n' ? 'domains' : 'revzones'),groupName($dbh,$curgroup)); 1929 2040 fill_fpnla($count); 1930 2041 … … 1937 2048 1938 2049 # set up the headers 1939 my @cols = ( 'domain', 'status', 'group');1940 my %colheads = (domain => 'Domain', status => 'Status', group => 'Group');2050 my @cols = (($webvar{revrec} eq 'n' ? 'domain' : 'revnet'), 'status', 'group'); 2051 my %colheads = (domain => 'Domain', revnet => 'Reverse Zone', status => 'Status', group => 'Group'); 1941 2052 fill_colheads($sortby, $sortorder, \@cols, \%colheads); 1942 2053 … … 1946 2057 1947 2058 # waffle, waffle - keep state on these as well as sortby, sortorder? 2059 ##fixme: put this higher so the count doesn't get munched? 1948 2060 $page->param("start$startwith" => 1) if $startwith && $startwith =~ /^(?:[a-z]|0-9)$/; 1949 2061 … … 1951 2063 $page->param(searchsubs => $searchsubs) if $searchsubs; 1952 2064 1953 ##fixme1954 ##fixme push the SQL and direct database fiddling off into a sub in DNSDB.pm1955 ##fixme1956 1957 2065 $page->param(group => $curgroup); 1958 my @domlist; 1959 $sql = "SELECT domain_id,domain,status,groups.group_name AS group FROM domains". 1960 " INNER JOIN groups ON domains.group_id=groups.group_id". 1961 " WHERE domains.group_id IN ($curgroup".($childlist ? ",$childlist" : '').")". 1962 ($startwith ? " AND domain ~* ?" : ''). 1963 ($filter ? " AND domain ~* ?" : ''). 1964 " ORDER BY ".($sortby eq 'group' ? 'groups.group_name' : $sortby). 1965 " $sortorder ".($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage); 1966 $sth = $dbh->prepare($sql); 1967 $sth->execute(@filterargs); 1968 my $rownum = 0; 1969 while (my @data = $sth->fetchrow_array) { 1970 my %row; 1971 $row{domainid} = $data[0]; 1972 $row{domain} = $data[1]; 1973 $row{status} = ($data[2] ? 'Active' : 'Inactive'); 1974 $row{group} = $data[3]; 1975 $row{bg} = ($rownum++)%2; 1976 $row{mkactive} = !$data[2]; 1977 $row{sid} = $sid; 1978 $row{offset} = $offset; 1979 # ACLs 1980 $row{domain_edit} = ($permissions{admin} || $permissions{domain_edit}); 1981 $row{domain_delete} = ($permissions{admin} || $permissions{domain_delete}); 1982 push @domlist, \%row; 1983 } 1984 $page->param(domtable => \@domlist); 2066 2067 my $zonelist = getZoneList($dbh, (childlist => $childlist, curgroup => $curgroup, 2068 revrec => $webvar{revrec}, 2069 filter => ($filter ? $filter : undef), startwith => ($startwith ? $startwith : undef), 2070 offset => $webvar{offset}, sortby => $sortby, sortorder => $sortorder 2071 ) ); 2072 # probably don't need this, keeping for reference for now 2073 # foreach (@$zonelist) { 2074 # } 2075 $page->param(domtable => $zonelist); 1985 2076 } # end listdomains() 1986 2077 … … 2221 2312 my $groupid = shift; 2222 2313 my $entry = shift; 2314 my $revid = shift || 0; 2223 2315 2224 2316 ##fixme: push SQL into DNSDB.pm … … 2228 2320 my ($user_id, $fullname) = $sth->fetchrow_array; 2229 2321 2230 $sth = $dbh->prepare("INSERT INTO log (domain_id,user_id,group_id,email,name,entry ) ".2231 "VALUES (?,?,?,?,?,? )") or warn $dbh->errstr;2232 $sth->execute($domid,$user_id,$groupid,$username,$fullname,$entry ) or warn $sth->errstr;2322 $sth = $dbh->prepare("INSERT INTO log (domain_id,user_id,group_id,email,name,entry,rdns_id) ". 2323 "VALUES (?,?,?,?,?,?,?)") or warn $dbh->errstr; 2324 $sth->execute($domid,$user_id,$groupid,$username,$fullname,$entry,$revid) or warn $sth->errstr; 2233 2325 } # end logaction() 2234 2235 2236 ##fixme: generalize to return appropriate id on all cases (ie, use $partype)2237 sub parentID {2238 my $id = shift;2239 my $idtype = shift;2240 my $partype = shift;2241 my $defrec = shift || '';2242 2243 my $sql = '';2244 2245 if ($idtype eq 'dom') {2246 return $id if $defrec eq 'y'; # "domain" + default records, we're really looking at a group.2247 $sql = "SELECT group_id FROM domains WHERE domain_id=?";2248 } elsif ($idtype eq 'rec') {2249 if ($defrec eq 'y') {2250 $sql = "SELECT group_id FROM default_records WHERE record_id=?";2251 } else {2252 $sql = "SELECT d.group_id FROM domains d".2253 " INNER JOIN records r ON d.domain_id=r.domain_id".2254 " WHERE r.record_id=?";2255 }2256 } elsif ($idtype eq 'group') {2257 $sql = "SELECT parent_group_id FROM groups WHERE group_id=?";2258 } elsif ($idtype eq 'user') {2259 $sql = "SELECT group_id FROM users WHERE user_id=?";2260 } else {2261 return "FOO", "BAR"; # can't get here.... we think.2262 }2263 my $sth = $dbh->prepare($sql);2264 $sth->execute($id);2265 my ($retid) = $sth->fetchrow_array;2266 return $retid if $retid;2267 # ahh! fall of the edge of the world if things went sideways2268 ##fixme: really need to do a little more error handling, I think2269 } # end parentID()2270 2326 2271 2327 -
branches/stable/dns.sql
r215 r544 41 41 \. 42 42 43 CREATE TABLE default_rev_records ( 44 record_id serial NOT NULL, 45 group_id integer DEFAULT 1 NOT NULL, 46 host text DEFAULT '' NOT NULL, 47 "type" integer DEFAULT 1 NOT NULL, 48 val text DEFAULT '' NOT NULL, 49 ttl integer DEFAULT 86400 NOT NULL, 50 description text 51 ); 52 53 COPY default_rev_records (record_id, group_id, host, "type", val, ttl, description) FROM stdin; 54 1 1 hostmaster.ADMINDOMAIN:ns1.ADMINDOMAIN 6 3600:900:1048576:2560 3600 55 2 1 unused-%r.ADMINDOMAIN 65283 ZONE 3600 56 \. 57 43 58 CREATE TABLE domains ( 44 59 domain_id serial NOT NULL, 45 60 "domain" character varying(80) NOT NULL, 61 group_id integer DEFAULT 1 NOT NULL, 62 description character varying(255) DEFAULT ''::character varying NOT NULL, 63 status integer DEFAULT 1 NOT NULL, 64 zserial integer, 65 sertype character(1) DEFAULT 'D'::bpchar 66 ); 67 68 CREATE TABLE revzones ( 69 rdns_id serial NOT NULL, 70 revnet cidr NOT NULL, 46 71 group_id integer DEFAULT 1 NOT NULL, 47 72 description character varying(255) DEFAULT ''::character varying NOT NULL, … … 69 94 log_id serial NOT NULL, 70 95 domain_id integer, 96 rdns_id integer, 71 97 user_id integer, 72 98 group_id integer, … … 103 129 \. 104 130 105 -- fixme: need to handle looooong records (eg, SPF)131 -- rdns_id defaults to 0 since many records will not have an associated rDNS entry. 106 132 CREATE TABLE records ( 107 domain_id integer NOT NULL, 133 domain_id integer NOT NULL DEFAULT 0, 134 rdns_id integer NOT NULL DEFAULT 0, 108 135 record_id serial NOT NULL, 109 136 host text DEFAULT '' NOT NULL, … … 119 146 CREATE TABLE rectypes ( 120 147 val integer NOT NULL, 121 name character varying( 12) NOT NULL,148 name character varying(20) NOT NULL, 122 149 stdflag integer DEFAULT 1 NOT NULL, 123 150 listorder integer DEFAULT 255 NOT NULL, … … 129 156 COPY rectypes (val, name, stdflag, listorder, alphaorder) FROM stdin; 130 157 1 A 1 1 1 131 2 NS 1 2 37 132 3 MD 2 255 29 133 4 MF 2 255 30 134 5 CNAME 1 6 9 135 6 SOA 0 8 53 136 7 MB 3 255 28 137 8 MG 3 255 31 138 9 MR 3 255 33 139 10 NULL 3 255 43 140 11 WKS 3 255 64 141 12 PTR 2 4 46 142 13 HINFO 3 255 18 143 14 MINFO 3 255 32 144 15 MX 1 3 34 145 16 TXT 1 5 60 146 17 RP 2 255 48 147 18 AFSDB 3 255 4 148 19 X25 3 255 65 149 20 ISDN 3 255 21 150 21 RT 3 255 50 151 22 NSAP 3 255 38 152 23 NSAP-PTR 3 255 39 153 24 SIG 3 255 51 154 25 KEY 3 255 23 155 26 PX 3 255 47 156 27 GPOS 3 255 17 157 28 AAAA 1 2 3 158 29 LOC 3 255 25 159 30 NXT 3 255 44 160 31 EID 3 255 15 161 32 NIMLOC 3 255 36 162 33 SRV 1 7 55 163 34 ATMA 3 255 6 164 35 NAPTR 3 255 35 165 36 KX 3 255 24 166 37 CERT 3 255 8 167 38 A6 3 3 2 168 39 DNAME 3 255 12 169 40 SINK 3 255 52 170 41 OPT 3 255 45 171 42 APL 3 255 5 172 43 DS 3 255 14 173 44 SSHFP 3 255 56 174 45 IPSECKEY 3 255 20 175 46 RRSIG 3 255 49 176 47 NSEC 3 255 40 177 48 DNSKEY 3 255 13 178 49 DHCID 3 255 10 179 50 NSEC3 3 255 41 180 51 NSEC3PARAM 3 255 42 181 55 HIP 3 255 19 182 99 SPF 3 255 54 183 100 UINFO 3 255 62 184 101 UID 3 255 61 185 102 GID 3 255 16 186 103 UNSPEC 3 255 63 187 249 TKEY 3 255 58 188 250 TSIG 3 255 59 189 251 IXFR 3 255 22 190 252 AXFR 3 255 7 191 253 MAILB 3 255 27 192 254 MAILA 3 255 26 193 32768 TA 3 255 57 194 32769 DLV 3 255 11 158 2 NS 1 5 37 159 3 MD 5 255 29 160 4 MF 5 255 30 161 5 CNAME 1 7 9 162 6 SOA 0 0 53 163 7 MB 5 255 28 164 8 MG 5 255 31 165 9 MR 5 255 33 166 10 NULL 5 255 43 167 11 WKS 5 255 64 168 12 PTR 3 10 46 169 13 HINFO 5 255 18 170 14 MINFO 5 255 32 171 15 MX 1 6 34 172 16 TXT 1 8 60 173 17 RP 4 255 48 174 18 AFSDB 5 255 4 175 19 X25 5 255 65 176 20 ISDN 5 255 21 177 21 RT 5 255 50 178 22 NSAP 5 255 38 179 23 NSAP-PTR 5 255 39 180 24 SIG 5 255 51 181 25 KEY 5 255 23 182 26 PX 5 255 47 183 27 GPOS 5 255 17 184 28 AAAA 1 3 3 185 29 LOC 5 255 25 186 30 NXT 5 255 44 187 31 EID 5 255 15 188 32 NIMLOC 5 255 36 189 33 SRV 1 9 55 190 34 ATMA 5 255 6 191 35 NAPTR 5 255 35 192 36 KX 5 255 24 193 37 CERT 5 255 8 194 38 A6 5 3 2 195 39 DNAME 5 255 12 196 40 SINK 5 255 52 197 41 OPT 5 255 45 198 42 APL 5 255 5 199 43 DS 5 255 14 200 44 SSHFP 5 255 56 201 45 IPSECKEY 5 255 20 202 46 RRSIG 5 255 49 203 47 NSEC 5 255 40 204 48 DNSKEY 5 255 13 205 49 DHCID 5 255 10 206 50 NSEC3 5 255 41 207 51 NSEC3PARAM 5 255 42 208 55 HIP 5 255 19 209 99 SPF 5 255 54 210 100 UINFO 5 255 62 211 101 UID 5 255 61 212 102 GID 5 255 16 213 103 UNSPEC 5 255 63 214 249 TKEY 5 255 58 215 250 TSIG 5 255 59 216 251 IXFR 5 255 22 217 252 AXFR 5 255 7 218 253 MAILB 5 255 27 219 254 MAILA 5 255 26 220 32768 TA 5 255 57 221 32769 DLV 5 255 11 222 \. 223 224 -- Custom types (ab)using the "Private use" range from 65280 to 65534 225 COPY rectypes (val, name, stdflag, listorder, alphaorder) FROM stdin; 226 65280 A+PTR 2 2 2 227 65281 AAAA+PTR 2 4 4 228 65282 PTR template 3 11 2 229 65283 A+PTR template 3 12 2 230 65284 AAAA+PTR template 3 13 2 195 231 \. 196 232 … … 254 290 ADD CONSTRAINT "$1" FOREIGN KEY (group_id) REFERENCES groups(group_id); 255 291 256 ALTER TABLE ONLY records257 ADD CONSTRAINT "$1" FOREIGN KEY (domain_id) REFERENCES domains(domain_id);258 259 292 ALTER TABLE ONLY users 260 293 ADD CONSTRAINT "$1" FOREIGN KEY (group_id) REFERENCES groups(group_id); … … 264 297 265 298 -- set starting sequence numbers, since we've inserted data before they're active 266 SELECT pg_catalog.setval('misc_misc_id_seq', 1, true); 267 SELECT pg_catalog.setval('default_records_record_id_seq', 8, true); 299 SELECT pg_catalog.setval('misc_misc_id_seq', 2, false); 300 SELECT pg_catalog.setval('default_records_record_id_seq', 8, false); 301 SELECT pg_catalog.setval('default_rev_records_record_id_seq', 3, false); 268 302 SELECT pg_catalog.setval('domains_domain_id_seq', 1, false); 269 SELECT pg_catalog.setval('groups_group_id_seq', 1, true);270 SELECT pg_catalog.setval('permissions_permission_id_seq', 2, true);303 SELECT pg_catalog.setval('groups_group_id_seq', 2, false); 304 SELECT pg_catalog.setval('permissions_permission_id_seq', 3, false); 271 305 SELECT pg_catalog.setval('records_record_id_seq', 1, false); 272 306 SELECT pg_catalog.setval('users_user_id_seq', 2, false); -
branches/stable/templates/badpage.tmpl
r173 r544 1 1 <!-- <TMPL_VAR NAME=sid> --> 2 2 <div id="badpage"> 3 <TMPL_IF badpage> 3 4 Bad page requested: 4 5 <div class="errmsg"> … … 6 7 </div> 7 8 Press the 'Back' button on your browser to continue. 9 </TMPL_IF> 10 <TMPL_IF badtemplate> 11 Template error: 12 <div class="warnmsg"> 13 <TMPL_VAR NAME=badtemplate> 8 14 </div> 15 </TMPL_IF> 16 </div> -
branches/stable/templates/delrec.tmpl
r100 r544 6 6 <h3>Are you really sure you want to delete record:<br /> 7 7 <TMPL_VAR NAME=host> <TMPL_VAR NAME=ftype> <TMPL_VAR NAME=recval></h3> 8 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&del=cancel&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&parentid=<TMPL_VAR NAME=parentid>">cancel</a> | <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&del=ok&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&parentid=<TMPL_VAR NAME=parentid>">confirm</a> 8 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&del=cancel&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&parentid=<TMPL_VAR NAME=parentid>">cancel</a> 9 | 10 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&del=ok&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&parentid=<TMPL_VAR NAME=parentid>">confirm</a> 9 11 </td></tr></table> 10 12 -
branches/stable/templates/domlist.tmpl
r147 r544 9 9 </TMPL_IF> 10 10 <TMPL_IF errmsg> 11 <div class= 'errmsg'><TMPL_VAR NAME=errmsg></div>11 <div class="errmsg"><TMPL_VAR NAME=errmsg></div> 12 12 </TMPL_IF> 13 13 14 14 <table width="98%"> 15 <tr><th colspan="3"><div class="center maintitle"> Domainlist</div></th></tr>15 <tr><th colspan="3"><div class="center maintitle"><TMPL_IF domlist>Domain<TMPL_ELSE>Reverse zone</TMPL_IF> list</div></th></tr> 16 16 <tr> 17 17 <td class="leftthird"><TMPL_INCLUDE NAME="pgcount.tmpl"></td> … … 19 19 <td class="rightthird"><TMPL_INCLUDE NAME="sbox.tmpl"></td> 20 20 </tr> 21 <tr><td colspan="3" align="center"><TMPL_INCLUDE NAME="lettsearch.tmpl"></td></tr> 22 <tr><td colspan="3" align="right"><TMPL_IF domain_create><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=newdomain">New Domain</a></TMPL_IF></td></tr> 21 <TMPL_IF domlist><tr><td colspan="3" align="center"><TMPL_INCLUDE NAME="lettsearch.tmpl"></td></tr></TMPL_IF> 22 <tr><td colspan="3" align="right"> 23 <TMPL_IF domain_create> 24 <TMPL_IF domlist> 25 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=newdomain">New Domain</a> 26 <TMPL_ELSE> 27 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=newrevzone">New Reverse Zone</a> 28 </TMPL_IF> 29 </TMPL_IF> 30 </td></tr> 23 31 </table> 24 32 … … 37 45 <TMPL_IF name=domtable> 38 46 <TMPL_LOOP name=domtable> 39 <tr class="row<TMPL_ VAR name=bg>">40 <td align="left"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=reclist&id=<TMPL_VAR NAME=domainid>&defrec=n "><TMPL_VAR NAME=domain></a></td>41 <td><TMPL_ VAR name=status></td>47 <tr class="row<TMPL_IF __odd__>1<TMPL_ELSE>0</TMPL_IF>"> 48 <td align="left"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=reclist&id=<TMPL_VAR NAME=domainid>&defrec=n<TMPL_UNLESS domlist>&revrec=y</TMPL_UNLESS>"><TMPL_VAR NAME=domain></a></td> 49 <td><TMPL_IF status>Active<TMPL_ELSE>Inactive</TMPL_IF></td> 42 50 <td><TMPL_VAR name=group></td> 43 <TMPL_IF domain_edit> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page= domlist<TMPL_IF NAME=offset>&offset=<TMPL_VAR NAME=offset></TMPL_IF>&id=<TMPL_VAR NAME=domainid>&domstatus=<TMPL_IF NAME=mkactive>domon<TMPL_ELSE>domoff</TMPL_IF>"><TMPL_IF NAME=mkactive>activate<TMPL_ELSE>deactivate</TMPL_IF></a></td></TMPL_IF>44 <TMPL_IF domain_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page= deldom&id=<TMPL_VAR NAME=domainid>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>51 <TMPL_IF domain_edit> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage><TMPL_IF NAME=offset>&offset=<TMPL_VAR NAME=offset></TMPL_IF>&id=<TMPL_VAR NAME=domainid>&domstatus=<TMPL_IF status>domoff<TMPL_ELSE>domon</TMPL_IF>"><TMPL_IF status>deactivate<TMPL_ELSE>activate</TMPL_IF></a></td></TMPL_IF> 52 <TMPL_IF domain_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_IF domlist>deldom<TMPL_ELSE>delrevzone</TMPL_IF>&id=<TMPL_VAR NAME=domainid>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF> 45 53 </tr> 46 54 </TMPL_LOOP> 47 55 <TMPL_ELSE> 48 <tr><td colspan="5" align="center">No domainsfound</td></tr>56 <tr><td colspan="5" align="center">No <TMPL_IF domlist>domains<TMPL_ELSE>reverse zones</TMPL_IF> found</td></tr> 49 57 </TMPL_IF> 50 58 </table> -
branches/stable/templates/fpnla.tmpl
r87 r544 1 <TMPL_IF navfirst><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=0<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF> "><img src="images/frev.png" alt="[ First ]" />First</a><TMPL_ELSE><img src="images/frev.png" alt="[ First ]" />First</TMPL_IF> 2 <TMPL_IF navprev><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=prevoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF> "><img src="images/rev.png" alt="[ Previous ]" />Previous</a><TMPL_ELSE><img src="images/rev.png" alt="[ Previous ]" />Previous</TMPL_IF> 3 <TMPL_IF navnext><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=nextoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF> ">Next<img src="images/fwd.png" alt="[ Next ]" /></a><TMPL_ELSE>Next<img src="images/fwd.png" alt="[ Next ]" /></TMPL_IF> 4 <TMPL_IF navlast><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=lastoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF> ">Last<img src="images/ffwd.png" alt="[ Last ]" /></a><TMPL_ELSE>Last<img src="images/ffwd.png" alt="[ Last ]" /></TMPL_IF> 5 <TMPL_IF navall><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=all<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF> ">All</a><TMPL_ELSE><TMPL_UNLESS onepage><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=0<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF>"><TMPL_VAR NAME=perpage> per page</a></TMPL_UNLESS></TMPL_IF>1 <TMPL_IF navfirst><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=0<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VAR NAME=revrec></TMPL_IF>"><img src="images/frev.png" alt="[ First ]" />First</a><TMPL_ELSE><img src="images/frev.png" alt="[ First ]" />First</TMPL_IF> 2 <TMPL_IF navprev><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=prevoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VAR NAME=revrec></TMPL_IF>"><img src="images/rev.png" alt="[ Previous ]" />Previous</a><TMPL_ELSE><img src="images/rev.png" alt="[ Previous ]" />Previous</TMPL_IF> 3 <TMPL_IF navnext><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=nextoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">Next<img src="images/fwd.png" alt="[ Next ]" /></a><TMPL_ELSE>Next<img src="images/fwd.png" alt="[ Next ]" /></TMPL_IF> 4 <TMPL_IF navlast><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=<TMPL_VAR NAME=lastoffs><TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">Last<img src="images/ffwd.png" alt="[ Last ]" /></a><TMPL_ELSE>Last<img src="images/ffwd.png" alt="[ Last ]" /></TMPL_IF> 5 <TMPL_IF navall><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=all<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VAR NAME=revrec></TMPL_IF>">All</a><TMPL_ELSE><TMPL_UNLESS onepage><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=<TMPL_VAR NAME=curpage>&offset=0<TMPL_IF id>&id=<TMPL_VAR NAME=id></TMPL_IF><TMPL_IF defrec>&defrec=<TMPL_VAR NAME=defrec></TMPL_IF><TMPL_IF revrec>&revrec=<TMPL_VARNAME=revrec></TMPL_IF>"><TMPL_VAR NAME=perpage> per page</a></TMPL_UNLESS></TMPL_IF> -
branches/stable/templates/menu.tmpl
r214 r544 4 4 <hr /> 5 5 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=domlist">Domains</a><br /> 6 <TMPL_IF mayrdns><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=revzones">Reverse Zones</a><br /></TMPL_IF> 6 7 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=useradmin">Users</a><br /> 7 8 <a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=log">Log</a><br /> 8 <TMPL_IF maydefrec><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=reclist&id=<TMPL_VAR NAME=group>&defrec=y">Default Records</a><br /></TMPL_IF> 9 <TMPL_IF maydefrec><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=reclist&id=<TMPL_VAR NAME=group>&defrec=y">Default Records</a><br /> 10 <TMPL_IF mayrdns><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=reclist&id=<TMPL_VAR NAME=group>&defrec=y&revrec=y">Default Reverse Records</a><br /></TMPL_IF></TMPL_IF> 9 11 <TMPL_IF mayimport><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=axfr">AXFR Import</a><br /></TMPL_IF> 10 12 <TMPL_IF maybulk><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=bulkdomain">Bulk Domain Operations</a><br /></TMPL_IF> -
branches/stable/templates/reclist.tmpl
r422 r544 8 8 <div class="result"><TMPL_VAR NAME=resultmsg></div> 9 9 </TMPL_IF> 10 <TMPL_IF warnmsg> 11 <div class="warning"><TMPL_VAR NAME=warnmsg></div> 12 </TMPL_IF> 10 13 <TMPL_IF errmsg> 11 <div class= 'errmsg'><TMPL_VAR NAME=errmsg></div>14 <div class="errmsg"><TMPL_VAR NAME=errmsg></div> 12 15 </TMPL_IF> 13 16 … … 37 40 <input type="hidden" name="id" value="<TMPL_VAR NAME=id>" /> 38 41 <input type="hidden" name="defrec" value="<TMPL_VAR NAME=defrec>" /> 42 <input type="hidden" name="revrec" value="<TMPL_VAR NAME=revrec>" /> 39 43 <input name="filter"<TMPL_IF filter> value="<TMPL_VAR NAME=filter>"</TMPL_IF> /> 40 44 <input type="submit" value="Filter" /> … … 46 50 <td colspan="3">Records</td> 47 51 <td align="center"><a href="textrecs.cgi?sid=<TMPL_VAR NAME=sid>&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>">Plain text</a></td> 48 <TMPL_IF record_create> <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=record&parentid=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&re cact=new">Add record</a></td></TMPL_IF>49 <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=log&id=<TMPL_VAR NAME=id><TMPL_IF logdom>&ltype=dom</TMPL_IF> ">View log</a></td>52 <TMPL_IF record_create> <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=record&parentid=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&recact=new">Add record</a></td></TMPL_IF> 53 <td align="right"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=log&id=<TMPL_VAR NAME=id><TMPL_IF logdom>&ltype=dom</TMPL_IF><TMPL_IF logrdns>&ltype=rdns</TMPL_IF>">View log</a></td> 50 54 </tr> 51 55 … … 59 63 NAME=offset>&offset=<TMPL_VAR NAME=offset></TMPL_IF>&sortby=<TMPL_VAR 60 64 NAME=sortby>&order=<TMPL_VAR NAME=order>&id=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR 61 NAME=defrec>"><TMPL_VAR NAME=colname></a><TMPL_IF NAME=sortorder> <img alt="<TMPL_VAR 62 NAME=sortorder>" src="images/<TMPL_VAR NAME=sortorder>.png" /></TMPL_IF></td></TMPL_LOOP> 65 NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>"><TMPL_VAR NAME=colname></a><TMPL_IF 66 NAME=sortorder> <img alt="<TMPL_VAR NAME=sortorder>" src="images/<TMPL_VAR NAME=sortorder>.png" 67 /></TMPL_IF></td></TMPL_LOOP> 63 68 <TMPL_IF record_delete> <td>Delete</td></TMPL_IF> 64 69 </tr> 65 70 <TMPL_LOOP NAME=reclist> 66 71 <tr class="row<TMPL_VAR NAME=row>"> 67 <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=record&parentid=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&recact=edit&id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=host></a><TMPL_ELSE><TMPL_VAR NAME=host></TMPL_IF></td> 72 <TMPL_IF fwdzone> 73 <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=record&parentid=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&recact=edit&id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=host></a><TMPL_ELSE><TMPL_VAR NAME=host></TMPL_IF></td> 68 74 <td><TMPL_VAR NAME=type></td> 69 75 <td><TMPL_VAR NAME=val></td> … … 71 77 <td><TMPL_VAR NAME=weight></td> 72 78 <td><TMPL_VAR NAME=port></td> 79 <TMPL_ELSE> 80 <td><TMPL_IF record_edit><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=record&parentid=<TMPL_VAR NAME=id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&recact=edit&id=<TMPL_VAR NAME=record_id>"><TMPL_VAR NAME=val></a><TMPL_ELSE><TMPL_VAR NAME=val></TMPL_IF></td> 81 <td><TMPL_VAR NAME=type></td> 82 <td><TMPL_VAR NAME=host></td> 83 </TMPL_IF> 73 84 <td><TMPL_VAR NAME=ttl></td> 74 <TMPL_IF record_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&id=<TMPL_VAR NAME=record_id>&defrec=<TMPL_VAR NAME=defrec>& parentid=<TMPL_VAR NAME=id>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF>85 <TMPL_IF record_delete> <td align="center"><a href="dns.cgi?sid=<TMPL_VAR NAME=sid>&page=delrec&id=<TMPL_VAR NAME=record_id>&defrec=<TMPL_VAR NAME=defrec>&revrec=<TMPL_VAR NAME=revrec>&parentid=<TMPL_VAR NAME=id>"><img src="images/trash2.png" alt="[ Delete ]" /></a></td></TMPL_IF> 75 86 </tr> 76 87 </TMPL_LOOP> -
branches/stable/templates/record.tmpl
r155 r544 14 14 <input type="hidden" name="page" value="record" /> 15 15 <input type="hidden" name="defrec" value="<TMPL_VAR NAME=defrec>" /> 16 <input type="hidden" name="revrec" value="<TMPL_VAR NAME=revrec>" /> 16 17 <input type="hidden" name="sid" value="<TMPL_VAR NAME=sid>" /> 17 18 <input type="hidden" name="parentid" value="<TMPL_VAR NAME=parentid>" /> … … 23 24 24 25 <table border="0" cellspacing="2" cellpadding="2" width="100%"> 25 <TMPL_IF failed> <tr><td class="errhead" colspan="2">Error <TMPL_VAR NAME=wastrying> record: <TMPL_VAR NAME=errmsg></td></tr></TMPL_IF>26 <TMPL_IF failed> <tr><td class="errhead" colspan="2">Error <TMPL_VAR NAME=wastrying> record: <TMPL_VAR NAME=errmsg></td></tr></TMPL_IF> 26 27 <tr class="tableheader"><td align="center" colspan="2"><TMPL_VAR NAME=todo>: <TMPL_VAR NAME=dohere></td></tr> 27 28 <tr class="datalinelight"> 29 <TMPL_IF fwdzone> 28 30 <td>Hostname</td> 29 31 <td><input type="text" name="name" value="<TMPL_VAR NAME=name>" /></td> 32 <TMPL_ELSE> 33 <td>IP Address</td> 34 <td><input type="text" name="address" value="<TMPL_VAR ESCAPE=HTML NAME=address>" /></td> 35 </TMPL_IF> 30 36 </tr> 31 37 <tr class="datalinelight"> … … 38 44 </tr> 39 45 <tr class="datalinelight"> 46 <TMPL_IF fwdzone> 40 47 <td>Address</td> 41 48 <td><input type="text" name="address" value="<TMPL_VAR ESCAPE=HTML NAME=address>" /></td> 49 <TMPL_ELSE> 50 <td>Hostname</td> 51 <td><input type="text" name="name" value="<TMPL_VAR NAME=name>" /></td> 52 </TMPL_IF> 42 53 </tr> 54 <TMPL_IF fwdzone> 43 55 <tr class="datalinelight"> 44 56 <td>Distance (MX and SRV only)</td> … … 53 65 <td><input type="text" name="port" value="<TMPL_VAR NAME=port>" size="5" maxlength="10" /></td> 54 66 </tr> 67 </TMPL_IF> 55 68 <tr class="datalinelight"> 56 69 <td>TTL</td>
Note:
See TracChangeset
for help on using the changeset viewer.