sub require_bind
{
return if ($require_bind++);
&foreign_require("bind8", "bind8-lib.pl");
%bconfig = &foreign_config("bind8");
}
# check_depends_dns(&domain)
# For a sub-domain that is being added to a parent DNS domain, make sure the
# parent zone actually exists
sub check_depends_dns
{
if ($_[0]->{'subdom'}) {
local $tmpl = &get_template($_[0]->{'template'});
local $parent = &get_domain($_[0]->{'subdom'});
if ($tmpl->{'dns_sub'} && !$parent->{'dns'}) {
return $text{'setup_edepdnssub'};
}
}
return undef;
}
# setup_dns(&domain)
# Set up a zone for a domain
sub setup_dns
{
&require_bind();
local $tmpl = &get_template($_[0]->{'template'});
if (!$_[0]->{'subdom'} || $tmpl->{'dns_sub'} ne 'yes') {
# Creating a new real zone
&$first_print($text{'setup_bind'});
local $conf = &bind8::get_config();
local $base = $bconfig{'master_dir'} ? $bconfig{'master_dir'} :
&bind8::base_directory($conf);
local $file = &bind8::automatic_filename($_[0]->{'dom'}, 0, $base);
local $dir = {
'name' => 'zone',
'values' => [ $_[0]->{'dom'} ],
'type' => 1,
'members' => [ { 'name' => 'type',
'values' => [ 'master' ] },
{ 'name' => 'file',
'values' => [ $file ] } ]
};
if ($tmpl->{'namedconf'} &&
$tmpl->{'namedconf'} ne 'none') {
push(@{$dir->{'members'}},
&text_to_named_conf($tmpl->{'namedconf'}));
}
local @slaves = &bind8::list_slave_servers();
if (@slaves) {
# Also notify slave servers, unless already added
local ($also) = grep { $_->{'name'} eq 'also-notify' }
@{$dir->{'members'}};
if (!$also) {
$also = { 'name' => 'also-notify',
'type' => 1,
'members' => [ ] };
foreach my $s (@slaves) {
push(@{$also->{'members'}},
{ 'name' => &to_ipaddress($s->{'host'}) });
}
push(@{$dir->{'members'}}, $also);
push(@{$dir->{'members'}},
{ 'name' => 'notify',
'values' => [ 'yes' ] });
}
}
local $pconf;
local $indent = 0;
if ($tmpl->{'dns_view'}) {
# Adding inside a view. This may use named.conf, or an include
# file references inside the view, if any
$pconf = &bind8::get_config_parent();
local $view = &get_bind_view($conf, $tmpl->{'dns_view'});
print STDERR "viewname=$tmpl->{'dns_view'} view=$view\n";
if ($view) {
local $addfile = &bind8::add_to_file();
local $addfileok;
if ($bind8::config{'zones_file'} &&
$view->{'file'} ne $bind8::config{'zones_file'}) {
# BIND module config asks for a file .. make sure
# it is included in the view
foreach my $vm (@{$view->{'members'}}) {
if ($vm->{'file'} eq $addfile) {
# Add file is OK
$addfileok = 1;
}
}
}
print STDERR "addfileok=$addfileok\n";
if (!$addfileok) {
# Add to named.conf
$pconf = $view;
$indent = 1;
$dir->{'file'} = $view->{'file'};
}
else {
# Add to the file
$dir->{'file'} = $addfile;
$pconf = &bind8::get_config_parent($addfile);
}
}
else {
&error(&text('setup_ednsview', $tmpl->{'dns_view'}));
}
}
else {
# Adding at top level .. but perhaps in a different file
$dir->{'file'} = &bind8::add_to_file();
$pconf = &bind8::get_config_parent($dir->{'file'});
}
&lock_file($dir->{'file'});
&bind8::save_directive($pconf, undef, [ $dir ], $indent);
&flush_file_lines();
&unlock_file($dir->{'file'});
unlink($bind8::zone_names_cache);
undef(@bind8::list_zone_names_cache);
# Create the records file
local %zd;
&bind8::get_zone_defaults(\%zd);
local $rootfile = &bind8::make_chroot($file);
local $ip = $_[0]->{'dns_ip'} || $_[0]->{'ip'};
if (!-r $rootfile) {
&lock_file($rootfile);
&create_standard_records($file, $_[0], $ip);
&bind8::set_ownership($rootfile);
&unlock_file($rootfile);
}
&$second_print($text{'setup_done'});
# Create on slave servers
local $myip = $bconfig{'this_ip'} ||
&to_ipaddress(&get_system_hostname());
if (@slaves && !$_[0]->{'noslaves'}) {
local $slaves = join(" ", map { $_->{'host'} } @slaves);
&$first_print(&text('setup_bindslave', $slaves));
local @slaveerrs = &bind8::create_on_slaves($_[0]->{'dom'},
$myip);
if (@slaveerrs) {
&$second_print($text{'setup_eslaves'});
foreach $sr (@slaveerrs) {
&$second_print($sr->[0]->{'host'}." : ".
$sr->[1]);
}
}
else {
&$second_print($text{'setup_done'});
}
$_[0]->{'dns_slave'} = $slaves;
}
undef(@bind8::get_config_cache);
}
else {
# Creating a sub-domain - add to parent's DNS zone
local $parent = &get_domain($_[0]->{'subdom'});
&$first_print(&text('setup_bindsub', $parent->{'dom'}));
local $z = &get_bind_zone($parent->{'dom'});
if (!$z) {
&error(&text('setup_ednssub', $parent->{'dom'}));
}
local $file = &bind8::find("file", $z->{'members'});
local $fn = $file->{'values'}->[0];
&lock_file(&bind8::make_chroot($fn));
$_[0]->{'dns_submode'} = 1; # So we know how this was done
local $ipdom = $_[0]->{'virt'} ? $_[0] : $parent;
local $ip = $ipdom->{'dns_ip'} || $ipdom->{'ip'};
&create_standard_records($fn, $_[0], $ip);
local @recs = &bind8::read_zone_file($fn, $parent->{'dom'});
&bind8::bump_soa_record($nfn, \@recs);
&unlock_file(&bind8::make_chroot($fn));
&$second_print($text{'setup_done'});
}
®ister_post_action(\&restart_bind);
}
sub slave_error_handler
{
$slave_error = $_[0];
}
# delete_dns(&domain)
# Delete a domain from the BIND config
sub delete_dns
{
&require_bind();
if (!$_[0]->{'dns_submode'}) {
&$first_print($text{'delete_bind'});
local $z = &get_bind_zone($_[0]->{'dom'});
if ($z) {
# Delete the records file
local $file = &bind8::find("file", $z->{'members'});
if ($file) {
local $zonefile =
&bind8::make_chroot($file->{'values'}->[0]);
&unlink_logged($zonefile);
local $logfile = $zonefile.".log";
if (!-r $logfile) { $logfile = $zonefile.".jnl"; }
if (-r $logfile) {
&unlink_logged($logfile);
}
}
# Delete from named.conf
local $rootfile = &bind8::make_chroot($z->{'file'});
&lock_file($rootfile);
local $lref = &read_file_lines($rootfile);
splice(@$lref, $z->{'line'}, $z->{'eline'} - $z->{'line'} + 1);
&flush_file_lines();
&unlock_file($rootfile);
# Clear zone names caches
unlink($bind8::zone_names_cache);
undef(@bind8::list_zone_names_cache);
&$second_print($text{'setup_done'});
}
else {
&$second_print($text{'save_nobind'});
}
local @slaves = split(/\s+/, $_[0]->{'dns_slave'});
if (@slaves) {
# Delete from slave servers too
&$first_print(&text('delete_bindslave', $_[0]->{'dns_slave'}));
local @slaveerrs = &bind8::delete_on_slaves(
$_[0]->{'dom'}, \@slaves);
if (@slaveerrs) {
&$second_print($text{'delete_bindeslave'});
foreach $sr (@slaveerrs) {
&$second_print($sr->[0]->{'host'}." : ".
$sr->[1]);
}
}
else {
&$second_print($text{'setup_done'});
}
delete($_[0]->{'dns_slave'});
}
}
else {
# Delete records from parent zone
local $parent = &get_domain($_[0]->{'subdom'});
&$first_print(&text('delete_bindsub', $parent->{'dom'}));
local $z = &get_bind_zone($parent->{'dom'});
if (!$z) {
&$second_print($text{'save_nobind'});
return;
}
local $file = &bind8::find("file", $z->{'members'});
local $fn = $file->{'values'}->[0];
&lock_file(&bind8::make_chroot($fn));
local @recs = &bind8::read_zone_file($fn, $parent->{'dom'});
foreach $r (reverse(@recs)) {
if ($r->{'name'} =~ /$_[0]->{'dom'}/) {
&bind8::delete_record($fn, $r);
}
}
&bind8::bump_soa_record($fn, \@recs);
&unlock_file(&bind8::make_chroot($fn));
&$second_print($text{'setup_done'});
$_[0]->{'dns_submode'} = 0;
}
®ister_post_action(\&restart_bind);
}
# modify_dns(&domain, &olddomain)
# If the IP for this server has changed, update all records containing the old
# IP to the new.
sub modify_dns
{
&require_bind();
local $tmpl = &get_template($_[0]->{'template'});
local $z;
local ($oldzonename, $newzonename);
if ($_[0]->{'dns_submode'}) {
# Get parent domain
local $parent = &get_domain($_[0]->{'subdom'});
$z = &get_bind_zone($parent->{'dom'});
$oldzonename = $newzonename = $parent->{'dom'};
}
else {
# Get this domain
$z = &get_bind_zone($_[1]->{'dom'});
$newzonename = $_[1]->{'dom'};
$oldzonename = $_[1]->{'dom'};
}
return 0 if (!$z); # No DNS zone!
local $oldip = $_[1]->{'dns_ip'} || $_[1]->{'ip'};
local $newip = $_[0]->{'dns_ip'} || $_[0]->{'ip'};
local $rv = 0;
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
local $nfn;
local $file = &bind8::find("file", $z->{'members'});
if (!$_[0]->{'dns_submode'}) {
# Domain name has changed .. rename zone file
&$first_print($text{'save_dns2'});
&lock_file(&bind8::make_chroot($z->{'file'}));
local $fn = $file->{'values'}->[0];
$nfn = $fn;
$nfn =~ s/$_[1]->{'dom'}/$_[0]->{'dom'}/;
if ($fn ne $nfn) {
&rename_logged(&bind8::make_chroot($fn),
&bind8::make_chroot($nfn))
}
$file->{'values'}->[0] = $nfn;
$file->{'value'} = $nfn;
# Change zone in .conf file
$z->{'values'}->[0] = $_[0]->{'dom'};
$z->{'value'} = $_[0]->{'dom'};
&bind8::save_directive(&bind8::get_config_parent(),
[ $z ], [ $z ], 0);
&flush_file_lines();
&unlock_file(&bind8::make_chroot($z->{'file'}));
}
else {
&$first_print($text{'save_dns6'});
$nfn = $file->{'values'}->[0];
}
# Modify any records containing the old name
&lock_file(&bind8::make_chroot($nfn));
local @recs = &bind8::read_zone_file($nfn, $oldzonename);
foreach my $r (@recs) {
if ($r->{'name'} =~ /$_[1]->{'dom'}/i) {
$r->{'name'} =~ s/$_[1]->{'dom'}/$_[0]->{'dom'}/;
if ($r->{'type'} eq 'SPF') {
# Fix SPF TXT record
$r->{'values'}->[0] =~
s/$_[1]->{'dom'}/$_[0]->{'dom'}/;
}
if ($r->{'type'} eq 'MX') {
# Fix mail server in MX record
$r->{'values'}->[1] =~
s/$_[1]->{'dom'}/$_[0]->{'dom'}/;
}
&bind8::modify_record($nfn, $r, $r->{'name'},
$r->{'ttl'}, $r->{'class'},
$r->{'type'},
&join_record_values($r),
$r->{'comment'});
}
}
# Update SOA record
&bind8::bump_soa_record($nfn, \@recs);
&unlock_file(&bind8::make_chroot($nfn));
$rv++;
# Clear zone names caches
unlink($bind8::zone_names_cache);
undef(@bind8::list_zone_names_cache);
&$second_print($text{'setup_done'});
if (!$_[0]->{'dns_submode'}) {
local @slaves = split(/\s+/, $_[0]->{'dns_slave'});
if (@slaves) {
# Rename on slave servers too
&$first_print(&text('save_dns3', $_[0]->{'dns_slave'}));
local @slaveerrs = &bind8::rename_on_slaves(
$_[1]->{'dom'}, $_[0]->{'dom'}, \@slaves);
if (@slaveerrs) {
&$second_print($text{'save_bindeslave'});
foreach $sr (@slaveerrs) {
&$second_print($sr->[0]->{'host'}." : ".
$sr->[1]);
}
}
else {
&$second_print($text{'setup_done'});
}
}
}
}
if ($oldip ne $newip) {
# IP address has changed .. need to update any records that use
# the old IP
&$first_print($text{'save_dns'});
local $file = &bind8::find("file", $z->{'members'});
local $fn = $file->{'values'}->[0];
local $zonefile = &bind8::make_chroot($fn);
&lock_file($zonefile);
local @recs = &bind8::read_zone_file($fn, $newzonename);
foreach $r (@recs) {
if ($r->{'values'}->[0] eq $oldip) {
# Change IP in A record
&bind8::modify_record($fn, $r, $r->{'name'},
$r->{'ttl'}, $r->{'class'},
$r->{'type'}, $newip,
$r->{'comment'});
}
elsif ($r->{'type'} eq 'SPF' &&
$r->{'values'}->[0] =~ /$oldip/) {
# Fix IP within an SPF
$r->{'values'}->[0] =~ s/$oldip/$newip/g;
&bind8::modify_record($fn, $r, $r->{'name'},
$r->{'ttl'}, $r->{'class'},
$r->{'type'},
&join_record_values($r),
$r->{'comment'});
}
}
# Update SOA record
&bind8::bump_soa_record($fn, \@recs);
&unlock_file($zonefile);
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'mail'} && !$_[1]->{'mail'} && !$tmpl->{'dns_replace'}) {
# Email was enabled .. add MX records
local $file = &bind8::find("file", $z->{'members'});
local $fn = $file->{'values'}->[0];
local $zonefile = &bind8::make_chroot($fn);
&lock_file($zonefile);
local @recs = &bind8::read_zone_file($fn, $newzonename);
local ($mx) = grep { $_->{'type'} eq 'MX' &&
$_->{'name'} eq $_[0]->{'dom'}."." ||
$_->{'type'} eq 'A' &&
$_->{'name'} eq "mail.".$_[0]->{'dom'}."." } @recs;
if (!$mx) {
&$first_print($text{'save_dns4'});
local $ip = $_[0]->{'dns_ip'} || $_[0]->{'ip'};
&create_mx_records($fn, $_[0], $ip);
&bind8::bump_soa_record($fn, \@recs);
&$second_print($text{'setup_done'});
$rv++;
}
&unlock_file($zonefile);
}
elsif (!$_[0]->{'mail'} && $_[1]->{'mail'} && !$tmpl->{'dns_replace'}) {
# Email was disabled .. remove MX records
local $file = &bind8::find("file", $z->{'members'});
local $zonefile = &bind8::make_chroot($file);
&lock_file($zonefile);
local $fn = $file->{'values'}->[0];
local @recs = &bind8::read_zone_file($fn, $newzonename);
local @mx = grep { $_->{'type'} eq 'MX' &&
$_->{'name'} eq $_[0]->{'dom'}."." ||
$_->{'type'} eq 'A' &&
$_->{'name'} eq "mail.".$_[0]->{'dom'}."." } @recs;
if (@mx) {
&$first_print($text{'save_dns5'});
foreach my $r (reverse(@mx)) {
&bind8::delete_record($fn, $r);
}
&bind8::bump_soa_record($fn, \@recs);
&$second_print($text{'setup_done'});
$rv++;
}
&unlock_file($zonefile);
}
if ($_[0]->{'mx_servers'} ne $_[1]->{'mx_servers'}) {
# Secondary MX servers have been changed - add or remove MX records
&$first_print($text{'save_dns7'});
local @newmxs = split(/\s+/, $_[0]->{'mx_servers'});
local @oldmxs = split(/\s+/, $_[1]->{'mx_servers'});
local $file = &bind8::find("file", $z->{'members'});
local $zonefile = &bind8::make_chroot($file);
&lock_file($zonefile);
local $fn = $file->{'values'}->[0];
local @recs = &bind8::read_zone_file($fn, $newzonename);
&foreign_require("servers", "servers-lib.pl");
local %servers = map { $_->{'id'}, $_ }
(&servers::list_servers(), &list_mx_servers());
local $withdot = $_[0]->{'dom'}.".";
# Add missing MX records
foreach my $id (@newmxs) {
if (&indexof($id, @oldmxs) < 0) {
# A new MX .. add a record for it, if there isn't one
local $s = $servers{$id};
local $mxhost = $s->{'mxname'} || $s->{'host'};
local $already = 0;
foreach my $r (@recs) {
if ($r->{'type'} eq 'MX' &&
$r->{'name'} eq $withdot &&
$r->{'values'}->[1] eq $mxhost.".") {
$already = 1;
}
}
if (!$already) {
&bind8::create_record($fn, $withdot, undef,
"IN", "MX", "10 $mxhost.");
}
}
}
# Remove those that are no longer needed
local @mxs;
foreach my $id (@oldmxs) {
if (&indexof($id, @newmxs) < 0) {
# An old MX .. remove it
local $s = $servers{$id};
local $mxhost = $s->{'mxname'} || $s->{'host'};
foreach my $r (@recs) {
if ($r->{'type'} eq 'MX' &&
$r->{'name'} eq $withdot &&
$r->{'values'}->[1] eq $mxhost.".") {
push(@mxs, $r);
}
}
}
}
foreach my $r (reverse(@mxs)) {
&bind8::delete_record($fn, $r);
}
&bind8::bump_soa_record($fn, \@recs);
&$second_print($text{'setup_done'});
$rv++;
}
®ister_post_action(\&restart_bind) if ($rv);
return $rv;
}
# join_record_values(&record)
# Given the values for a record, joins them into a space-separated string
# with quoting if needed
sub join_record_values
{
local ($r) = @_;
local @rv;
foreach my $v (@{$r->{'values'}}) {
push(@rv, $v =~ /\s/ ? "\"$v\"" : $v);
}
return join(" ", @rv);
}
# create_mx_records(file, &domain, ip)
# Adds MX records to a DNS domain
sub create_mx_records
{
local ($file, $d, $ip) = @_;
local $withdot = $d->{'dom'}.".";
&bind8::create_record($file, "mail.$withdot", undef,
"IN", "A", $ip);
&bind8::create_record($file, $withdot, undef,
"IN", "MX", "5 mail.$withdot");
# Add MX records for slaves
local %ids = map { $_, 1 }
split(/\s+/, $d->{'mx_servers'});
local @servers = grep { $ids{$_->{'id'}} } &list_mx_servers();
local $n = 10;
foreach my $s (@servers) {
local $mxhost = $s->{'mxname'} || $s->{'host'};
&bind8::create_record($file, $withdot, undef,
"IN", "MX", "$n $mxhost.");
$n += 5;
}
}
# create_standard_records(file, &domain, ip)
# Adds to a records file the needed records for some domain
sub create_standard_records
{
local ($file, $d, $ip) = @_;
local $rootfile = &bind8::make_chroot($file);
local $tmpl = &get_template($d->{'template'});
local $serial = $bconfig{'soa_style'} ?
&bind8::date_serial().sprintf("%2.2d", $bconfig{'soa_start'}) :
time();
if (!$tmpl->{'dns_replace'}) {
# Create records that are appropriate for this domain
if (!$d->{'dns_submode'}) {
# Only add SOA if this is a new file, not a sub-domain
&open_tempfile(RECS, ">$rootfile");
if ($bconfig{'master_ttl'}) {
&print_tempfile(RECS,
"\$ttl $zd{'minimum'}$zd{'minunit'}\n");
}
&close_tempfile(RECS);
local $tmaster = $tmpl->{'dns_master'} eq 'none' ? undef :
$tmpl->{'dns_master'};
local $master = $tmaster ||
$bconfig{'default_prins'} ||
&get_system_hostname();
$master .= "." if ($master !~ /\.$/);
local $email = $bconfig{'tmpl_email'} ||
"root\@$master";
$email = &bind8::email_to_dotted($email);
local $soa = "$master $email (\n".
"\t\t\t$serial\n".
"\t\t\t$zd{'refresh'}$zd{'refunit'}\n".
"\t\t\t$zd{'retry'}$zd{'retunit'}\n".
"\t\t\t$zd{'expiry'}$zd{'expunit'}\n".
"\t\t\t$zd{'minimum'}$zd{'minunit'} )";
&bind8::create_record($file, "@", undef, "IN",
"SOA", $soa);
&bind8::create_record($file, "@", undef, "IN",
"NS", $master);
local $slave;
local @slaves = &bind8::list_slave_servers();
foreach $slave (@slaves) {
local @bn = $slave->{'nsname'} ||
gethostbyname($slave->{'host'});
local $full = "$bn[0].";
&bind8::create_record($file, "@", undef, "IN",
"NS", "$bn[0].");
}
}
local $withdot = $d->{'dom'}.".";
&bind8::create_record($file, $withdot, undef,
"IN", "A", $ip);
&bind8::create_record($file, "www.$withdot", undef,
"IN", "A", $ip);
&bind8::create_record($file, "ftp.$withdot", undef,
"IN", "A", $ip);
&bind8::create_record($file, "m.$withdot", undef,
"IN", "A", $ip);
if ($d->{'mail'}) {
# For mail domains, add MX to this server
&create_mx_records($file, $d, $ip);
}
if ($tmpl->{'dns_spf'} ne "none") {
# Add SPF record for domain
local $str = &bind8::join_spf(&default_domain_spf($d));
&bind8::create_record($file, $withdot, undef,
"IN", "TXT", "\"$str\"");
}
}
if ($tmpl->{'dns'} && (!$d->{'dns_submode'} || !$tmpl->{'dns_replace'})) {
# Add or use the user-defined template
&open_tempfile(RECS, ">>$rootfile");
local %subs = %$d;
$subs{'serial'} = $serial;
$subs{'dnsemail'} = $d->{'emailto'};
$subs{'dnsemail'} =~ s/\@/./g;
local $recs = &substitute_domain_template(
join("\n", split(/\t+/, $tmpl->{'dns'}))."\n", \%subs);
&print_tempfile(RECS, $recs);
&close_tempfile(RECS);
}
}
# validate_dns(&domain)
# Check for the DNS domain and records file
sub validate_dns
{
local ($d) = @_;
local $z;
if ($d->{'dns_submode'} && $d->{'subdom'}) {
# Records are in parent domain's file
local $parent = &get_domain($d->{'subdom'});
$z = &get_bind_zone($parent->{'dom'});
}
else {
# Domain has its own records file
$z = &get_bind_zone($d->{'dom'});
}
return &text('validate_edns', "<tt>$d->{'dom'}</tt>") if (!$z);
local $file = &bind8::find("file", $z->{'members'});
return &text('validate_ednsfile', "<tt>$d->{'dom'}</tt>") if (!$file);
local $zonefile = &bind8::make_chroot(
&bind8::absolute_path($file->{'values'}->[0]));
return &text('validate_ednsfile2', "<tt>$zonefile</tt>") if (!-r $zonefile);
local @recs = &bind8::read_zone_file($file->{'values'}->[0], $d->{'dom'});
local %got;
foreach my $r (@recs) {
$got{lc($r->{'type'})}++;
}
$got{'SOA'} || &text('validate_ednssoa', "<tt>$zonefile</tt>");
$got{'A'} || &text('validate_ednsa', "<tt>$zonefile</tt>");
return undef;
}
# disable_dns(&domain)
# Re-names this domain in named.conf with the .disabled suffix
sub disable_dns
{
&$first_print($text{'disable_bind'});
&require_bind();
local $z = &get_bind_zone($_[0]->{'dom'});
if ($z) {
local $rootfile = &bind8::make_chroot($z->{'file'});
&lock_file($rootfile);
$z->{'values'}->[0] = $_[0]->{'dom'}.".disabled";
&bind8::save_directive(&bind8::get_config_parent(), [ $z ], [ $z ], 0);
&flush_file_lines();
&unlock_file($rootfile);
# Clear zone names caches
unlink($bind8::zone_names_cache);
undef(@bind8::list_zone_names_cache);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_bind);
}
else {
&$second_print($text{'save_nobind'});
}
}
# enable_dns(&domain)
# Re-names this domain in named.conf to remove the .disabled suffix
sub enable_dns
{
&$first_print($text{'enable_bind'});
&require_bind();
local $z = &get_bind_zone($_[0]->{'dom'});
if ($z) {
local $rootfile = &bind8::make_chroot($z->{'file'});
&lock_file($rootfile);
$z->{'values'}->[0] = $_[0]->{'dom'};
&bind8::save_directive(&bind8::get_config_parent(), [ $z ], [ $z ], 0);
&flush_file_lines();
&unlock_file($rootfile);
# Clear zone names caches
unlink($bind8::zone_names_cache);
undef(@bind8::list_zone_names_cache);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_bind);
}
else {
&$second_print($text{'save_nobind'});
}
}
# get_bind_zone(name, [&config], [file])
# Returns the zone structure for the named domain, possibly with .disabled
sub get_bind_zone
{
&require_bind();
local $conf = $_[1] ? $_[1] :
$_[2] ? [ &bind8::read_config_file($_[2]) ] :
&bind8::get_config();
local @zones = &bind8::find("zone", $conf);
local ($v, $z);
foreach $v (&bind8::find("view", $conf)) {
push(@zones, &bind8::find("zone", $v->{'members'}));
}
local ($z) = grep { lc($_->{'value'}) eq lc($_[0]) ||
lc($_->{'value'}) eq lc("$_[0].disabled") } @zones;
return $z;
}
# restart_bind(&domain)
# Signal BIND to re-load its configuration
sub restart_bind
{
&$first_print($text{'setup_bindpid'});
local $pid = &get_bind_pid();
if ($pid) {
if ($bconfig{'restart_cmd'}) {
&system_logged("$bconfig{'restart_cmd'} >/dev/null 2>&1 </dev/null");
}
else {
&kill_logged('HUP', $pid);
}
&$second_print($text{'setup_done'});
$rv = 1;
}
else {
&$second_print($text{'setup_notrun'});
$rv = 0;
}
if (&bind8::list_slave_servers()) {
# Re-start on slaves too
&$first_print(&text('setup_bindslavepids'));
local @slaveerrs = &bind8::restart_on_slaves();
if (@slaveerrs) {
&$second_print($text{'setup_bindeslave'});
foreach $sr (@slaveerrs) {
&$second_print($sr->[0]->{'host'}." : ".$sr->[1]);
}
}
else {
&$second_print($text{'setup_done'});
}
}
return $rv;
}
# check_dns_clash(&domain, [changing])
# Returns 1 if a domain already exists in BIND
sub check_dns_clash
{
if (!$_[1] || $_[1] eq 'dom') {
local ($czone) = &get_bind_zone($_[0]->{'dom'});
return $czone ? 1 : 0;
}
return 0;
}
# get_bind_pid()
# Returns the BIND PID, if it is running
sub get_bind_pid
{
&require_bind();
local $pidfile = &bind8::get_pid_file();
return &check_pid_file(&bind8::make_chroot($pidfile, 1));
}
# backup_dns(&domain, file)
# Save all the virtual server's DNS records as a separate file
sub backup_dns
{
&require_bind();
return 1 if ($_[0]->{'dns_submode'}); # backed up in parent
&$first_print($text{'backup_dnscp'});
local $z = &get_bind_zone($_[0]->{'dom'});
if ($z) {
local $file = &bind8::find("file", $z->{'members'});
local $filename = &bind8::make_chroot(
&bind8::absolute_path($file->{'values'}->[0]));
&execute_command("cp ".quotemeta($filename)." ".quotemeta($_[1]));
&$second_print($text{'setup_done'});
return 1;
}
else {
&$second_print($text{'backup_dnsnozone'});
return 0;
}
}
# restore_dns(&domain, file, &options)
# Update the virtual server's DNS records from the backup file, except the SOA
sub restore_dns
{
&require_bind();
return 1 if ($_[0]->{'dns_submode'}); # restored in parent
&$first_print($text{'restore_dnscp'});
local $z = &get_bind_zone($_[0]->{'dom'});
if ($z) {
local $file = &bind8::find("file", $z->{'members'});
local $filename = &bind8::make_chroot(
&bind8::absolute_path($file->{'values'}->[0]));
if ($_[2]->{'wholefile'}) {
# Copy whole file
&lock_file($filename);
©_source_dest($_[1], $filename);
&bind8::set_ownership($filename);
}
else {
# Only copy section after SOA
local $srclref = &read_file_lines($_[1]);
local $dstlref = &read_file_lines($filename);
&lock_file($filename);
local ($srcstart, $srcend) = &except_soa($_[0], $_[1]);
local ($dststart, $dstend) = &except_soa($_[0], $filename);
splice(@$dstlref, $dststart, $dstend - $dststart + 1,
@$srclref[$srcstart .. $srcend]);
&flush_file_lines();
}
# Need to bump SOA
local $fn = $file->{'values'}->[0];
local @recs = &bind8::read_zone_file($fn, $_[0]->{'dom'});
&bind8::bump_soa_record($file->{'values'}->[0], \@recs);
# Need to update IP addresses
local $r;
local ($baserec) = grep { $_->{'type'} eq "A" &&
($_->{'name'} eq $_[0]->{'dom'}."." ||
$_->{'name'} eq '@') } @recs;
local $ip = $_[0]->{'dns_ip'} || $_[0]->{'ip'};
local $baseip = $baserec ? $baserec->{'values'}->[0] : undef;
foreach $r (@recs) {
if ($r->{'type'} eq "A" &&
$r->{'values'}->[0] eq $baseip) {
&bind8::modify_record($fn, $r, $r->{'name'},
$r->{'ttl'},$r->{'class'},
$r->{'type'},
$_[0]->{'ip'},
$r->{'comment'});
}
}
&unlock_file($filename);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_bind);
return 1;
}
else {
&$second_print($text{'backup_dnsnozone'});
return 0;
}
}
# except_soa(&domain, file)
sub except_soa
{
local $bind8::config{'chroot'} = "/"; # make sure path is absolute
local @recs = &bind8::read_zone_file($_[1], $_[0]->{'dom'});
local ($r, $start, $end);
foreach $r (@recs) {
if ($r->{'type'} ne "SOA" && !$r->{'generate'} && !$r->{'defttl'} &&
!defined($start)) {
$start = $r->{'line'};
}
$end = $r->{'eline'};
}
return ($start, $end);
}
# get_bind_view([&conf], view)
# Returns the view object for the view to add domains to
sub get_bind_view
{
&require_bind();
local $conf = $_[0] || &bind8::get_config();
local @views = &bind8::find("view", $conf);
local ($view) = grep { $_->{'values'}->[0] eq $_[1] } @views;
return $view;
}
# show_restore_dns(&options)
# Returns HTML for DNS restore option inputs
sub show_restore_dns
{
local ($opts, $d) = @_;
return &ui_checkbox("dns_wholefile", 1, $text{'restore_dnswholefile'},
$opts->{'wholefile'});
}
# parse_restore_dns(&in)
# Parses the inputs for DNS restore options
sub parse_restore_dns
{
local ($in, $d) = @_;
return { 'wholefile' => $in->{'dns_wholefile'} };
}
# sysinfo_dns()
# Returns the BIND version
sub sysinfo_dns
{
&require_bind();
if (!$bind8::bind_version) {
local $out = `$bind8::config{'named_path'} -v 2>&1`;
if ($out =~ /(bind|named)\s+([0-9\.]+)/i) {
$bind8::bind_version = $2;
}
}
return ( [ $text{'sysinfo_bind'}, $bind8::bind_version ] );
}
# links_dns(&domain)
# Returns a link to the BIND module
sub links_dns
{
local ($d) = @_;
if ($config{'avail_dns'} && !$d->{'dns_submode'}) {
return ( { 'mod' => 'bind8',
'desc' => $text{'links_dns'},
'page' => "edit_master.cgi?zone=".&urlize($d->{'dom'}),
'cat' => 'services',
} );
}
return ( );
}
sub startstop_dns
{
local ($typestatus) = @_;
local $bpid = defined($typestatus{'bind8'}) ?
$typestatus{'bind8'} == 1 : &get_bind_pid();
if ($bpid && kill(0, $bpid)) {
return ( { 'status' => 1,
'name' => $text{'index_bname'},
'desc' => $text{'index_bstop'},
'restartdesc' => $text{'index_brestart'},
'longdesc' => $text{'index_bstopdesc'} } );
}
else {
return ( { 'status' => 0,
'name' => $text{'index_bname'},
'desc' => $text{'index_bstart'},
'longdesc' => $text{'index_bstartdesc'} } );
}
}
sub start_service_dns
{
&require_bind();
return &bind8::start_bind();
}
sub stop_service_dns
{
&require_bind();
return &bind8::stop_bind();
}
# show_template_dns(&tmpl)
# Outputs HTML for editing BIND related template options
sub show_template_dns
{
local ($tmpl) = @_;
&require_bind();
$conf = &bind8::get_config();
@views = &bind8::find("view", $conf);
# DNS records
local $ndi = &none_def_input("dns", $tmpl->{'dns'}, $text{'tmpl_dnsbelow'}, 1,
0, undef, [ "dns", "bind_replace", @views ? ( "newdns_view" ) : ( ) ]);
print &ui_table_row(&hlink($text{'tmpl_dns'}, "template_dns"),
$ndi."<br>\n".
&ui_textarea("dns", $tmpl->{'dns'} eq "none" ? "" :
join("\n", split(/\t/, $tmpl->{'dns'})),
10, 60)."<br>\n".
&ui_radio("bind_replace", int($tmpl->{'dns_replace'}),
[ [ 0, $text{'tmpl_replace0'} ],
[ 1, $text{'tmpl_replace1'} ] ]));
# Option for view to add to, for BIND 9
if (@views) {
print &ui_table_row($text{'newdns_view'},
&ui_select("view", $config{'dns_view'},
[ [ "", $text{'newdns_noview'} ],
map { [ $_->{'values'}->[0] ] } @views ]));
}
print &ui_table_hr();
# Master NS hostnames
print &ui_table_row(&hlink($text{'tmpl_dnsmaster'},
"template_dns_master"),
&none_def_input("dns_master", $tmpl->{'dns_master'},
$text{'tmpl_dnsmnames'}, 0, 0, $text{'tmpl_dnsmauto'},
[ "dns_master" ])." ".
&ui_textbox("dns_master", $tmpl->{'dns_master'} eq 'none' ? '' :
$tmpl->{'dns_master'}, 40));
# Option for SPF record
print &ui_table_row(&hlink($text{'tmpl_spf'},
"template_dns_spf_mode"),
&none_def_input("dns_spf", $tmpl->{'dns_spf'},
$text{'tmpl_spfyes'}, 0, 0, $text{'no'},
[ "dns_spfhosts", "dns_spfall", "dns_sub_mode" ]));
# Extra SPF hosts
print &ui_table_row(&hlink($text{'tmpl_spfhosts'},
"template_dns_spfhosts"),
&ui_textbox("dns_spfhosts", $tmpl->{'dns_spfhosts'}, 40));
# SPF ~all mode
print &ui_table_row(&hlink($text{'tmpl_spfall'},
"template_dns_spfall"),
&ui_yesno_radio("dns_spfall", $tmpl->{'dns_spfall'} ? 1 : 0));
# Add sub-domains to parent domain DNS
print &ui_table_row(&hlink($text{'tmpl_dns_sub'},
"template_dns_sub"),
&none_def_input("dns_sub", $tmpl->{'dns_sub'},
$text{'yes'}, 0, 0, $text{'no'}));
# Extra named.conf directives
print &ui_table_hr();
print &ui_table_row(&hlink($text{'tmpl_namedconf'}, "namedconf"),
&none_def_input("namedconf", $tmpl->{'namedconf'},
$text{'tmpl_namedconfbelow'}, 0, 0, undef,
[ "namedconf" ])."<br>".
&ui_textarea("namedconf",
$tmpl->{'namedconf'} eq 'none' ? '' :
join("\n", split(/\t/, $tmpl->{'namedconf'})),
5, 60));
}
# parse_template_dns(&tmpl)
# Updates BIND related template options from %in
sub parse_template_dns
{
local ($tmpl) = @_;
# Save DNS settings
$tmpl->{'dns'} = &parse_none_def("dns");
if ($in{"dns_mode"} == 2) {
$tmpl->{'default'} || $tmpl->{'dns'} ||
&error($text{'tmpl_edns'});
$tmpl->{'dns_replace'} = $in{'bind_replace'};
$tmpl->{'dns_view'} = $in{'view'};
&require_bind();
local $fakeip = "1.2.3.4";
local $fakedom = "foo.com";
local $recs = &substitute_template(
join("\n", split(/\t+/, $in{'dns'}))."\n",
{ 'ip' => $fakeip,
'dom' => $fakedom });
local $temp = &transname();
&open_tempfile(TEMP, ">$temp");
&print_tempfile(TEMP, $recs);
&close_tempfile(TEMP);
$bind8::config{'short_names'} = 0; # force canonicalization
$bind8::config{'chroot'} = '/'; # turn off chroot for temp path
$bind8::config{'auto_chroot'} = undef;
local @recs = &bind8::read_zone_file($temp, $fakedom);
unlink($temp);
foreach $r (@recs) {
$soa++ if ($r->{'name'} eq $fakedom."." &&
$r->{'type'} eq "SOA");
$ns++ if ($r->{'name'} eq $fakedom."." &&
$r->{'type'} eq "NS");
$dom++ if ($r->{'name'} eq $fakedom."." &&
($r->{'type'} eq "A" || $r->{'type'} eq "MX"));
$www++ if ($r->{'name'} eq "www.".$fakedom."." &&
$r->{'type'} eq "A" ||
$r->{'type'} eq "CNAME");
}
if ($in{'bind_replace'}) {
# Make sure an SOA and NS records exist
$soa == 1 || &error($text{'newdns_esoa'});
$ns || &error($text{'newdns_ens'});
$dom || &error($text{'newdns_edom'});
$www || &error($text{'newdns_ewww'});
}
else {
# Make sure SOA doesn't exist
$soa && &error($text{'newdns_esoa2'});
}
}
# Save NS hostname
$in{'dns_master_mode'} != 2 ||
($in{'dns_master'} =~ /^[a-z0-9\.\-\_]+$/i && $in{'dns_master'} =~ /\./) ||
&error($text{'tmpl_ednsmaster'});
$tmpl->{'dns_master'} = $in{'dns_master_mode'} == 0 ? "none" :
$in{'dns_master_mode'} == 1 ? undef : $in{'dns_master'};
# Save SPF
$tmpl->{'dns_spf'} = $in{'dns_spf_mode'} == 0 ? "none" :
$in{'dns_spf_mode'} == 1 ? undef : "yes";
$tmpl->{'dns_spfhosts'} = $in{'dns_spfhosts'};
$tmpl->{'dns_spfall'} = $in{'dns_spfall'};
# Save sub-domain DNS mode
$tmpl->{'dns_sub'} = $in{'dns_sub_mode'} == 0 ? "none" :
$in{'dns_sub_mode'} == 1 ? undef : "yes";
# Save named.conf
$tmpl->{'namedconf'} = &parse_none_def("namedconf");
if ($in{'namedconf_mode'} == 2) {
# Make sure the directives are valid
local @recs = &text_to_named_conf($tmpl->{'namedconf'});
if ($tmpl->{'namedconf'} =~ /\S/ && !@recs) {
&error($text{'newdns_enamedconf'});
}
}
}
# get_domain_spf(&domain)
# Returns the SPF object for a domain from its DNS records, or undef.
sub get_domain_spf
{
local ($d) = @_;
local @recs = &get_domain_dns_records($d);
foreach my $r (@recs) {
if ($r->{'type'} eq 'SPF' &&
$r->{'name'} eq $d->{'dom'}.'.') {
return &bind8::parse_spf($r->{'values'}->[0]);
}
}
return undef;
}
# save_domain_spf(&domain, &spf)
# Updates/creates/deletes a domain's SPF record.
sub save_domain_spf
{
local ($d, $spf) = @_;
local @recs = &get_domain_dns_records($d);
return if (!@recs); # Domain not found!
local ($r) = grep { $_->{'type'} eq 'SPF' &&
$_->{'name'} eq $d->{'dom'}.'.' } @recs;
local $str = $spf ? &bind8::join_spf($spf) : undef;
if ($r && $spf) {
# Update record
&bind8::modify_record($r->{'file'}, $r, $r->{'name'}, $r->{'ttl'},
$r->{'class'}, $r->{'type'}, "\"$str\"",
$r->{'comment'});
}
elsif ($r && !$spf) {
# Remove record
&bind8::delete_record($r->{'file'}, $r);
}
elsif (!$r && $spf) {
# Add record
&bind8::create_record($recs[0]->{'file'}, $d->{'dom'}.'.', undef,
"IN", "TXT", "\"$str\"");
}
else {
return;
}
&bind8::bump_soa_record($recs[0]->{'file'}, \@recs);
®ister_post_action(\&restart_bind);
}
# get_domain_dns_records(&domain)
# Returns an array of DNS records for a domain, or empty if the file couldn't
# be found.
sub get_domain_dns_records
{
local ($d) = @_;
&require_bind();
local $z = &get_bind_zone($d->{'dom'});
return ( ) if (!$z);
local $file = &bind8::find("file", $z->{'members'});
return ( ) if (!$file);
local $fn = $file->{'values'}->[0];
return &bind8::read_zone_file($fn, $d->{'dom'});
}
# default_domain_spf(&domain)
# Returns a default SPF object for a domain, based on its template
sub default_domain_spf
{
local ($d) = @_;
local $tmpl = &get_template($d->{'template'});
local $defip = &get_default_ip();
local $spf = { 'a' => 1, 'mx' => 1,
'a:' => [ $d->{'dom'} ],
'ip4:' => [ $defip ] };
local $hosts = &substitute_domain_template(
$tmpl->{'dns_spfhosts'}, $d);
foreach my $h (split(/\s+/, $hosts)) {
if (&check_ipaddress($h) ||
$h =~ /^(\S+)\// &&
&check_ipaddress($1)) {
push(@{$spf->{'ip4:'}}, $h);
}
else {
push(@{$spf->{'a:'}}, $h);
}
}
$spf->{'all'} = $tmpl->{'dns_spfall'} ? 2 : 1;
return $spf;
}
# text_to_named_conf(text)
# Converts a text string which contains zero or more BIND directives into an
# array of directive objects.
sub text_to_named_conf
{
local ($str) = @_;
local $temp = &transname();
&open_tempfile(TEMP, ">$temp");
&print_tempfile(TEMP, $str);
&close_tempfile(TEMP);
&require_bind();
local %oldconfig = %bind8::config; # turn off chroot temporarily
$bind8::config{'chroot'} = undef;
$bind8::config{'auto_chroot'} = undef;
local @rv = grep { $_->{'name'} ne 'dummy' }
&bind8::read_config_file($temp, 0);
%bind8::config = %oldconfig;
return @rv;
}
$done_feature_script{'dns'} = 1;
1;
syntax highlighted by Code2HTML, v. 0.9.1