# bind8-lib.pl
# Common functions for bind8 config files

do '../web-lib.pl';
&init_config();
do 'records-lib.pl';

if (open(VERSION, "$module_config_directory/version")) {
	chop($bind_version = <VERSION>);
	close(VERSION);
	}

# get_config()
# Returns an array of references to assocs, each containing the details of
# one directive
sub get_config
{
if (!@get_config_cache) {
	@get_config_cache = &read_config_file($config{'named_conf'});
	}
return \@get_config_cache;
}

# get_config_parent()
# Returns a structure containing the top-level config as members
sub get_config_parent
{
local $conf = &get_config();
return { 'file' => $config{'named_conf'},
	 'type' => 1,
	 'line' => -1,
	 'eline' => $lines_count{$config{'named_conf'}},
	 'members' => $conf };
}

# read_config_file(file, [expand includes])
# Reads a config file and returns an array of values
sub read_config_file
{
local($lnum, $line, $cmode, @ltok, @lnum, @tok,
      @rv, $i, $t, $j, $ifile, @inc, $str);
$lnum = 0;
open(FILE, $config{'chroot'}.$_[0]);
while($line = <FILE>) {
	# strip comments
	$line =~ s/\r|\n//g;
	$line =~ s/#.*$//g;
	$line =~ s/\/\/.*$//g if ($line !~ /".*\/\/.*"/);
	$line =~ s/\/\*.*\*\///g;
	while(1) {
		if (!$cmode && $line =~ /\/\*/) {
			# start of a C-style comment
			$cmode = 1;
			$line =~ s/\/\*.*$//g;
			}
		elsif ($cmode) {
			if ($line =~ /\*\//) {
				# end of comment
				$cmode = 0;
				$line =~ s/^.*\*\///g;
				}
			else { $line = ""; last; }
			}
		else { last; }
		}

	# split line into tokens
	undef(@ltok);
	while(1) {
		if ($line =~ /^\s*\"([^"]*)"(.*)$/) {
			push(@ltok, $1); $line = $2;
			}
		elsif ($line =~ /^\s*([{};])(.*)$/) {
			push(@ltok, $1); $line = $2;
			}
		elsif ($line =~ /^\s*([^{}; \t]+)(.*)$/) {
			push(@ltok, $1); $line = $2;
			}
		else { last; }
		}
	foreach $t (@ltok) {
		push(@tok, $t); push(@lnum, $lnum);
		}
	$lnum++;
	}
close(FILE);
$lines_count{$_[0]} = $lnum;

# parse tokens into data structures
$i = 0; $j = 0;
while($i < @tok) {
	$str = &parse_struct(\@tok, \@lnum, \$i, $j++, $_[0]);
	if ($str) { push(@rv, $str); }
	}

# expand include directives
for($i=0; $i<@rv; $i++) {
	if (lc($rv[$i]->{'name'}) eq "include" && !$_[1]) {
		# found one.. replace the include directive with it
		$ifile = $rv[$i]->{'value'};
		if ($ifile !~ /^\//) {
			$ifile = &base_directory(\@rv)."/$ifile";
			}
		@inc = &read_config_file($ifile, 1);

		# update index of included structures
		for($j=0; $j<@inc; $j++) {
			$inc[$j]->{'index'} += $rv[$i]->{'index'};
			}

		# update index of structures after include
		for($j=$i+1; $j<@rv; $j++) {
			$rv[$j]->{'index'} += scalar(@inc) - 1;
			}
		splice(@rv, $i--, 1, @inc);
		}
	}

return @rv;
}

# parse_struct(&tokens, &lines, &line_num, index, file)
# A structure can either have one value, or a list of values.
# Pos will end up at the start of the next structure
sub parse_struct
{
local(%str, $i, $t, @vals, $str);
$i = ${$_[2]};
$str{'name'} = lc($_[0]->[$i]);
$str{'line'} = $_[1]->[$i];
while(1) {
	$t = $_[0]->[++$i];
	if ($t eq "{" || $t eq ";" || $t eq "}") { last; }
	elsif (!defined($t)) { ${$_[2]} = $i; return undef; }
	else { push(@vals, $t); }
	}
$str{'values'} = \@vals;
$str{'value'} = $vals[0];
$str{'index'} = $_[3];
$str{'file'} = $_[4];
if ($t eq "{") {
	# contains sub-structures.. parse them
	local(@mems, $j);
	$i++;		# skip {
	$str{'type'} = 1;
	$j = 0;
	while($_[0]->[$i] ne "}") {
		if (!defined($_[0]->[$i])) { ${$_[2]} = $i; return undef; }
		$str = &parse_struct($_[0], $_[1], \$i, $j++, $_[4]);
		if ($str) { push(@mems, $str); }
		}
	$str{'members'} = \@mems;
	$i += 2;	# skip trailing } and ;
	}
else {
	# only a single value..
	$str{'type'} = 0;
	if ($t eq ";") {
		$i++;	# skip trailing ;
		}
	}
$str{'eline'} = $_[1]->[$i-1];	# ending line is the line number the trailing
				# ; is on
${$_[2]} = $i;
return \%str;
}

# find(name, &array)
sub find
{
local($c, @rv);
foreach $c (@{$_[1]}) {
	if ($c->{'name'} eq $_[0]) {
		push(@rv, $c);
		}
	}
return @rv ? wantarray ? @rv : $rv[0]
           : wantarray ? () : undef;
}

# find_value(name, &array)
sub find_value
{
local(@v);
@v = &find($_[0], $_[1]);
if (!@v) { return undef; }
elsif (wantarray) { return map { $_->{'value'} } @v; }
else { return $v[0]->{'value'}; }
}

# base_directory([&config])
# Returns the base directory for named files
sub base_directory
{
local($opts, $dir, $conf);
$conf = $_[0] ? $_[0] : &get_config();
if (($opts = &find("options", $conf)) &&
    ($dir = &find("directory", $opts->{'members'}))) {
	return $dir->{'value'};
	}
$config{'named_conf'} =~ /^(.*)\/[^\/]+$/;
return $1;
}

# save_directive(&parent, name|&old, &values, indent, [structonly])
# Given a structure containing a directive name, type, values and members
# add, update or remove that directive in config structure and data files.
# Updating of files assumes that there is no overlap between directives -
# each line in the config file must contain part or all of only one directive.
sub save_directive
{
local(@oldv, @newv, $pm, $i, $o, $n, $lref, @nl);
$pm = $_[0]->{'members'};
@oldv = ref($_[1]) ? @{$_[1]} : &find($_[1], $pm);
@newv = @{$_[2]};
for($i=0; $i<@oldv || $i<@newv; $i++) {
	if ($i >= @oldv && !$_[5]) {
		# a new directive is being added.. put it at the end of
		# the parent
		if (!$_[4]) {
			$lref = &read_file_lines($config{'chroot'}.$_[0]->{'file'});
			@nl = &directive_lines($newv[$i], $_[3]);
			splice(@$lref, $_[0]->{'eline'}, 0, @nl);
			$newv[$i]->{'file'} = $_[0]->{'file'};
			$newv[$i]->{'line'} = $_[0]->{'eline'};
			$newv[$i]->{'eline'} =
				$_[0]->{'eline'} + scalar(@nl) - 1;
			&renumber(&get_config(), $_[0]->{'eline'},
				  $_[0]->{'file'}, scalar(@nl));
			}
		push(@$pm, $newv[$i]);
		}
	elsif ($i >= @oldv && $_[5]) {
		# a new directive is being added.. put it at the start of
		# the parent
		if (!$_[4]) {
			$lref = &read_file_lines($config{'chroot'}.$_[0]->{'file'});
			@nl = &directive_lines($newv[$i], $_[3]);
			splice(@$lref, $_[0]->{'line'}+1, 0, @nl);
			$newv[$i]->{'file'} = $_[0]->{'file'};
			$newv[$i]->{'line'} = $_[0]->{'line'}+1;
			$newv[$i]->{'eline'} =
				$_[0]->{'line'} + scalar(@nl);
			&renumber(&get_config(), $_[0]->{'line'},
				  $_[0]->{'file'}, scalar(@nl));
			}
		splice(@$pm, 0, 0, $newv[$i]);
		}
	elsif ($i >= @newv) {
		# a directive was deleted
		if (!$_[4]) {
			$lref = &read_file_lines($config{'chroot'}.$oldv[$i]->{'file'});
			$ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
			splice(@$lref, $oldv[$i]->{'line'}, $ol);
			&renumber(&get_config(), $oldv[$i]->{'eline'},
				  $oldv[$i]->{'file'}, -$ol);
			}
		splice(@$pm, &indexof($oldv[$i], @$pm), 1);
		}
	else {
		# updating some directive
		if (!$_[4]) {
			$lref = &read_file_lines($config{'chroot'}.$oldv[$i]->{'file'});
			@nl = &directive_lines($newv[$i], $_[3]);
			$ol = $oldv[$i]->{'eline'} - $oldv[$i]->{'line'} + 1;
			splice(@$lref, $oldv[$i]->{'line'}, $ol, @nl);
			$newv[$i]->{'file'} = $_[0]->{'file'};
			$newv[$i]->{'line'} = $oldv[$i]->{'line'};
			$newv[$i]->{'eline'} =
				$oldv[$i]->{'line'} + scalar(@nl) - 1;
			&renumber(&get_config(), $oldv[$i]->{'eline'},
				  $oldv[$i]->{'file'}, scalar(@nl) - $ol);
			}
		$pm->[&indexof($oldv[$i], @$pm)] = $newv[$i];
		}
	}
}

# directive_lines(&directive, tabs)
# Renders some directive into a number of lines of text
sub directive_lines
{
local(@rv, $v, $m, $i);
$rv[0] = "\t" x $_[1];
$rv[0] .= "$_[0]->{'name'}";
foreach $v (@{$_[0]->{'values'}}) {
	if ($need_quote{$_[0]->{'name'}} && !$i) { $rv[0] .= " \"$v\""; }
	else { $rv[0] .= " $v"; }
	$i++;
	}
if ($_[0]->{'type'}) {
	# multiple values.. include them as well
	$rv[0] .= " {";
	foreach $m (@{$_[0]->{'members'}}) {
		push(@rv, &directive_lines($m, $_[1]+1));
		}
	push(@rv, ("\t" x ($_[1]+1))."}");
	}
$rv[$#rv] .= ";";
return @rv;
}

# renumber(&directives, line, file, count)
# Runs through the given array of directives and increases the line numbers
# of all those greater than some line by the given count
sub renumber
{
local($d);
foreach $d (@{$_[0]}) {
	if ($d->{'file'} eq $_[2]) {
		if ($d->{'line'} > $_[1]) { $d->{'line'} += $_[3]; }
		if ($d->{'eline'} > $_[1]) { $d->{'eline'} += $_[3]; }
		}
	if ($d->{'type'}) {
		&renumber($d->{'members'}, $_[1], $_[2], $_[3]);
		}
	}
}

# choice_input(text, name, &config, [display, option]+)
sub choice_input
{
local($rv, $v, $i, @ops);
$rv = "<td valign=top><b>$_[0]</b></td> <td valign=top>";
$v = &find_value($_[1], $_[2]);
for($i=3; $i<@_; $i+=2) {
	$rv .= sprintf "<input type=radio name=%s value='%s' %s> %s\n",
		$_[1], $_[$i+1], $v eq $_[$i+1] ? "checked" : "", $_[$i];
	}
return $rv."</td>\n";
}

# save_choice(name, &parent, indent)
sub save_choice
{
local($nd);
if ($in{$_[0]}) { $nd = { 'name' => $_[0], 'values' => [ $in{$_[0]} ] }; }
&save_directive($_[1], $_[0], $nd ? [ $nd ] : [ ], $_[2]);
}

# addr_match_input(text, name, &config)
# A field for editing a list of addresses, ACLs and partial IP addresses
sub addr_match_input
{
local($v, $rv, $av, @av);
$v = &find($_[1], $_[2]);
$rv = "<td valign=top><b>$_[0]</b></td> <td valign=top>";
$rv .= "<input type=radio name=$_[1]_def value=1 ".
       ($v ? "" : "checked")."> $text{'default'}";
$rv .= "<input type=radio name=$_[1]_def value=0 ".
       ($v ? "checked" : "")."> $text{'listed'}<br>";
foreach $av (@{$v->{'members'}}) { push(@av, $av->{'name'}); }
$rv .= "<textarea name=$_[1] rows=3 cols=15>".
	join("\n", @av)."</textarea></td>\n";
}

# save_addr_match(name, &parent, indent)
sub save_addr_match
{
local($addr, @vals, $dir);
if ($in{"$_[0]_def"}) { &save_directive($_[1], $_[0], [ ], $_[2]); }
else {
	foreach $addr (split(/\s+/, $in{$_[0]})) {
		push(@vals, { 'name' => $addr });
		}
	$dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
	&save_directive($_[1], $_[0], [ $dir ], $_[2]);
	}
}

# address_port_input(addresstext, portlabeltext, portnametext, defaulttext,
#                    addressname, portname, &config, size, type)
sub address_port_input
  {
    my $rv = &address_input($_[0], $_[4], $_[6], $_[8]);
    my $v = &find($_[4], $_[6]);

    my $port;
    for ($i = 0; $i < @{$v->{'values'}}; $i++) {
      if ($v->{'values'}->[$i] eq $_[5]) {
	$port = $v->{'values'}->[$i+1];
	last;
      }
    }
    my $n;
    ($n = $_[5]) =~ s/[^A-Za-z0-9_]/_/g;
    $rv .= "<td valign=top><b>$_[1]</b></td> <td nowrap valign=top>\n";
    $rv .= sprintf "<input type=radio name=${n}_def value=1 %s> $_[3]\n",
      defined($port) ? "" : "checked";
    $rv .= sprintf "<input type=radio name=${n}_def value = 0 %s> ",
      defined($port) ? "checked" : "";
    $rv .= sprintf "<input name=$n size=$_[7] value=\"%s\"> $_[2]</td>\n",
      defined($port) ? $port : "";

    return $rv;
  }

# address_input(text, name, &config, type)
sub address_input
{
local($v, $rv, $av, @av);
$v = &find($_[1], $_[2]);
foreach $av (@{$v->{'members'}}) {
	push(@av, join(" ", $av->{'name'}, @{$av->{'values'}}));
	}
if ($_[3] == 0) {
	# text area
	$rv = "<td valign=top><b>$_[0]</b></td> <td valign=top>";
	$rv .= "<textarea name=$_[1] rows=3 cols=15>".
		join("\n", @av)."</textarea></td>\n";
	}
else {
	$rv = "<td valign=top><b>$_[0]</b></td> <td colspan=3 valign=top>";
	$rv .= "<input name=$_[1] size=50 value=\"".join(' ',@av)."\"></td>\n";
	}
return $rv;
}

sub save_port_address {
  local($addr, $port, @vals, $dir);
  foreach $addr (split(/\s+/, $in{$_[0]})) {
    &check_ipaddress($addr) || &error(&text('eip', $addr));
    push(@vals, { 'name' => $addr });
  }
  $dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
  ($n = $_[1]) =~ s/[^A-Za-z0-9_]/_/g;
  $dir->{'values'} = [ $_[1], $in{$_[1]} ] if (!$in{"${n}_def"});
  &save_directive($_[2], $_[0], @vals ? [ $dir ] : [ ], $_[3]);
}

# save_address(name, &parent, indent, ips-only)
sub save_address
{
local ($addr, @vals, $dir, $i);
local @sp = split(/\s+/, $in{$_[0]});
for($i=0; $i<@sp; $i++) {
	!$_[3] || &check_ipaddress($sp[$i]) || &error(&text('eip', $sp[$i]));
	if (lc($sp[$i]) eq "key") {
		push(@vals, { 'name' => $sp[$i],
			      'values' => [ $sp[++$i] ] });
		}
	else {
		push(@vals, { 'name' => $sp[$i] });
		}
	}
$dir = { 'name' => $_[0], 'type' => 1, 'members' => \@vals };
&save_directive($_[1], $_[0], @vals ? [ $dir ] : [ ], $_[2]);
}

# opt_input(text, name, &config, default, size, units)
sub opt_input
{
local($v, $rv, $n);
$v = &find($_[1], $_[2]);
($n = $_[1]) =~ s/[^A-Za-z0-9_]/_/g;
$rv = "<td valign=top><b>$_[0]</b></td> <td nowrap valign=top";
$rv .= $_[4] > 30 ? " colspan=3>\n" : ">\n";
$rv .= sprintf "<input type=radio name=${n}_def value=1 %s> $_[3]\n",
	$v ? "" : "checked";
$rv .= sprintf "<input type=radio name=${n}_def value=0 %s> ",
	$v ? "checked" : "";
$rv .= sprintf "<input name=$n size=$_[4] value=\"%s\"> $_[5]</td>\n",
	$v ? $v->{'value'} : "";
return $rv;
}

sub save_opt
{
local($dir, $n);
($n = $_[0]) =~ s/[^A-Za-z0-9_]/_/g;
if ($in{"${n}_def"}) { &save_directive($_[2], $_[0], [ ], $_[3]); }
elsif ($err = &{$_[1]}($in{$n})) {
	&error($err);
	}
else {
	$dir = { 'name' => $_[0], 'values' => [ $in{$n} ] };
	&save_directive($_[2], $_[0], [ $dir ], $_[3]);
	}
}

# directives that need their value to be quoted
@need_quote = ( "file", "zone", "pid-file", "statistics-file",
	        "dump-file", "named-xfer", "secret" );
foreach $need (@need_quote) {
	$need_quote{$need}++;
	}

1;

# find_reverse(address, [view])
# Returns the zone and record structures for the PTR record for some address
sub find_reverse
{
local($conf, @zl, $rev, $z, $revconf, $revfile, $revrec, @revrecs, $addr, $rr,
      @octs, $i, @hexs, $ipv6, @zero);

# find reverse domain
$conf = &get_config();
if ($in{'view'} ne '') {
	$conf = $conf->[$in{'view'}]->{'members'};
	}
@zl = &find("zone", $conf);
$ipv6 = $config{'support_aaaa'} && &check_ip6address($_[0]);
if ($ipv6) {
	@zero = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
	$addr = &expandall_ip6($_[0]);
	$addr =~ s/://g;
	@hexs = split('', $addr);
	DOMAIN: for($i=30; $i>=0; $i--) {
		$addr = join(':',split(/(.{4})/,join('', (@hexs[0..$i],@zero[$i..30]))));
		$addr =~ s/::/:/g;
		$addr =~ s/(^:|:$)//g;
		$rev = &net_to_ip6int($addr, 4*($i+1));
		$rev =~ s/\.$//g;
		foreach $z (@zl) {
			if (lc($z->{'value'}) eq $rev &&
			    ($typed = &find("type", $z->{'members'})) &&
			    $typed->{'value'} eq "master") {
				# found the reverse master domain
				$revconf = $z;
				last DOMAIN;
				}
			}
		}
	}
else {
	@octs = split(/\./, $_[0]);
	DOMAIN: for($i=2; $i>=-1; $i--) {
		$rev = $i<0 ? "in-addr.arpa"
			    : &ip_to_arpa(join('.', @octs[0..$i]));
		$rev =~ s/\.$//g;
		foreach $z (@zl) {
			if ((lc($z->{'value'}) eq $rev ||
			     lc($z->{'value'}) eq "$rev.") &&
			    ($typed = &find("type", $z->{'members'})) &&
			    $typed->{'value'} eq "master") {
				# found the reverse master domain
				$revconf = $z;
				last DOMAIN;
				}
			}
		}
	}

# find reverse record
if ($revconf) {
	$revfile = &absolute_path(
		&find("file", $revconf->{'members'})->{'value'});
	@revrecs = &read_zone_file($revfile, $revconf->{'value'});
	if ($ipv6) {
		$addr = &net_to_ip6int($_[0], 128);
		}
	else {
		$addr = &ip_to_arpa($_[0]);
		}
	foreach $rr (@revrecs) {
		if ($rr->{'type'} eq "PTR" &&
		    lc($rr->{'name'}) eq lc($addr)) {
			# found the reverse record
			$revrec = $rr;
			last;
			}
		}
	}
return ($revconf, $revfile, $revrec);
}

# find_forward(address, [view])
# Returns the zone and record structures for the A record for some address
sub find_forward
{
local ($fwdconf, $i, $fwdfile, $fwdrec, $fr, $ipv6);

# find forward domain
local $host = $_[0]; $host =~ s/\.$//;
local $conf = &get_config();
if ($in{'view'} ne '') {
	$conf = $conf->[$in{'view'}]->{'members'};
	}
local @zl = &find("zone", $conf);
local @parts = split(/\./, $host);
DOMAIN: for($i=1; $i<@parts; $i++) {
	local $fwd = join(".", @parts[$i .. @parts-1]);
	foreach $z (@zl) {
		local $typed;
		if ((lc($z->{'value'}) eq $fwd ||
		     lc($z->{'value'}) eq "$fwd.") &&
		    ($typed = &find("type", $z->{'members'})) &&
		    $typed->{'value'} eq "master") {
			# Found the forward master!
			$fwdconf = $z;
			last DOMAIN;
			}
		}
	}

# find forward record
if ($fwdconf) {
	$fwdfile = &absolute_path(
		&find("file", $fwdconf->{'members'})->{'value'});
	local @fwdrecs = &read_zone_file($fwdfile, $fwdconf->{'value'});
	foreach $fr (@fwdrecs) {
		if ($ipv6 ? $fr->{'type'} eq "AAAA" : $fr->{'type'} eq "A" &&
		    $fr->{'name'} eq $_[0]) {
			# found the forward record!
			$fwdrec = $fr;
			last;
			}
		}
	}

return ($fwdconf, $fwdfile, $fwdrec);
}

# can_edit_zone(&access, &zone)
sub can_edit_zone
{
local %zcan;
local $zn = $_[1]->{'value'};

# Check zone name
if ($access{'zones'} eq '*') {
	}
elsif ($access{'zones'} =~ /^\!/) {
	foreach (split(/\s+/, $access{'zones'})) {
		return 0 if ($_ eq $zn);
		}
	}
else {
	local $ok;
	foreach (split(/\s+/, $access{'zones'})) {
		$ok++ if ($_ eq $zn);
		}
	return 0 if (!$ok);
	}

if ($_[0]->{'dironly'}) {
	# Check directory access control 
	local $filestr = &find("file", $_[1]->{'members'});
	return 1 if (!$filestr);
	local $file = &absolute_path($filestr->{'value'});
	return 0 if (!&allowed_zone_file($_[0], $file));
	}
return 1;
}

# record_input(zoneindex, view, type, file, origin, [num], [record])
# Display a form for editing or creating a DNS record
sub record_input
{
local(%rec, @recs, $ttl, $ttlunit);
local $type = $_[6] ? $_[6]->{'type'} : $_[2];
print "<form action=save_record.cgi>\n";
print "<input type=hidden name=index value='$_[0]'>\n";
print "<input type=hidden name=view value='$_[1]'>\n";
print "<input type=hidden name=file value='$_[3]'>\n";
print "<input type=hidden name=origin value='$_[4]'>\n";
print "<input type=hidden name=sort value='$in{'sort'}'>\n";
if (@_ >= 6) {
	print "<input type=hidden name=num value=$_[5]>\n";
	%rec = %{$_[6]};
	}
else { print "<input type=hidden name=new value=1>\n"; }
print "<input type=hidden name=type value='$type'>\n";
print "<input type=hidden name=redirtype value='$_[2]'>\n";
print "<table border>\n";
print "<tr $tb><td><b>",&text(@_ >= 6 ? 'edit_edit' : 'edit_add',
			      $text{"edit_".$type}),"</b></td> </tr>\n";
print "<tr $cb><td><table>\n";

if ($type eq "PTR") {
	print "<tr> <td><b>$text{'edit_addr'}</b></td>\n";
	printf "<td><input name=name value=\"%s\" size=30></td>\n",
		!%rec && $_[4] =~ /^(\d+)\.(\d+)\.(\d+)\.in-addr/ ?
			"$3.$2.$1." : &ip6int_to_net(&arpa_to_ip($rec{'name'}));
	}
elsif ($type eq "NS") {
	print "<tr> <td><b>$text{'edit_zonename'}</b></td>\n";
	print "<td><input name=name value=\"$rec{'name'}\" size=30></td>\n";
	}
elsif ($type eq "SRV") {
	local ($serv, $proto, $name) =
		$rec{'name'} =~ /^([^\.]+)\.([^\.]+)\.(\S+)/ ? ($1, $2, $3) :
			(undef, undef, undef);
	print "<tr> <td><b>$text{'edit_name'}</b></td>\n";
	print "<td><input name=name value=\"$name\" size=30></td>\n";

	print "<td><b>$text{'edit_proto'}</b></td>\n";
	print "<td><select name=proto>\n";
	printf "<option value=tcp %s>TCP\n", $proto eq 'tcp' ? 'selected' : '';
	printf "<option value=udp %s>UDP\n", $proto eq 'udp' ? 'selected' : '';
	print "<option value=$proto selected>".uc($proto)."\n"
		if ($proto && $proto ne 'tcp' && $proto ne 'udp');
	print "</select></td> </tr>\n";

	print "<tr> <td><b>$text{'edit_serv'}</b></td>\n";
	print "<td><input name=serv value=\"$serv\" size=20></td>\n";
	}
else {
	print "<tr> <td><b>$text{'edit_name'}</b></td>\n";
	print "<td><input name=name value=\"$rec{'name'}\" size=30></td>\n";
	}
if ($rec{'ttl'} =~ /^(\d+)([SMHDW]?)$/i) {
	$ttl = $1; $ttlunit = $2;
	}
else {
	$ttl = $rec{'ttl'}; $ttlunit = "";
	}
print "<td><b>$text{'edit_ttl'}</b></td>\n";
printf "<td><input type=radio name=ttl_def value=1 %s> $text{'default'}\n",
	defined($rec{'ttl'}) ? "" : "checked";
printf "<input type=radio name=ttl_def value=0 %s>\n",
	defined($rec{'ttl'}) ? "checked" : "";
print "<input name=ttl size=8 value=\"$ttl\">\n";
&time_unit_choice("ttlunit", $ttlunit);
print "</td> </tr>\n";

@v = @{$rec{'values'}};
if ($type eq "A" || $type eq "AAAA") {
	print "<tr> <td><b>$text{'value_A1'}</b></td>\n";
	print "<td><input name=value0 size=20 value=\"$v[0]\"></td> </tr>\n";
	if (@_ >= 6) {
		print "<input type=hidden name=oldname ",
		      "value=\"$rec{'name'}\">\n";
		print "<input type=hidden name=oldvalue0 value=\"$v[0]\">\n";
		}
	}
elsif ($type eq "NS") {
	print "<tr> <td><b>$text{'value_NS1'}</b></td>\n";
	print "<td colspan=3><input name=value0 size=30 value=\"$v[0]\">\n";
	print "($text{'edit_cnamemsg'})</td> </tr>\n";
	}
elsif ($type eq "CNAME") {
	print "<tr> <td><b>$text{'value_CNAME1'}</b></td>\n";
	print "<td colspan=3><input name=value0 size=30 value=\"$v[0]\">\n";
	print "($text{'edit_cnamemsg'})</td> </tr>\n";
	}
elsif ($type eq "MX") {
	print "<tr> <td><b>$text{'value_MX2'}</b></td>\n";
	print "<td><input name=value1 size=30 value=\"$v[1]\"></td>\n";
	print "<td><b>$text{'value_MX1'}</b></td>\n";
	print "<td><input name=value0 size=8 value=\"$v[0]\"></td> </tr>\n";
	}
elsif ($type eq "HINFO") {
	print "<tr> <td><b>$text{'value_HINFO1'}</b></td>\n";
	print "<td><input name=value0 size=20 value=\"$v[0]\"></td>\n";
	print "<td><b>$text{'value_HINFO2'}</b></td>\n";
	print "<td><input name=value1 size=20 value=\"$v[1]\"></td> </tr>\n";
	}
elsif ($type eq "TXT") {
	print "<tr> <td><b>$text{'value_TXT1'}</b></td>\n";
	print "<td><input name=value0 size=30 value=\"$v[0]\"></td> </tr>\n";
	}
elsif ($type eq "WKS") {
	print "<tr> <td><b>$text{'value_WKS1'}</b></td>\n";
	print "<td><input name=value0 size=15 value=\"$v[0]\"></td>\n";
	print "<td><b>$text{'value_WKS2'}</b></td>\n";
	print "<td><select name=value1>\n";
	printf "<option %s>TCP\n", $v[1] =~ /tcp/ ? "selected" : "";
	printf "<option %s>UDP\n", $v[1] =~ /udp/ ? "selected" : "";
	print "</select></td>\n";
	print "<tr> <td valign=top><b>$text{'value_WKS3'}</b></td>\n";
	print "<td><textarea name=value2 rows=3 cols=20 wrap>",
		join(' ', @v[2..$#v]),"</textarea></td> </tr>\n";
	}
elsif ($type eq "RP") {
	print "<tr> <td><b>$text{'value_RP1'}</b></td>\n";
	$v[0] = &dotted_to_email($v[0]);
	print "<td><input name=value0 size=20 value=\"$v[0]\"></td>\n";
	print "<td><b>$text{'value_RP2'}</b></td>\n";
	print "<td><input name=value1 size=30 value=\"$v[1]\"></td> </tr>\n";
	}
elsif ($type eq "PTR") {
	print "<tr> <td><b>$text{'value_PTR1'}</b></td>\n";
	print "<td><input name=value0 size=30 value=\"$v[0]\"></td> </tr>\n";
	if (@_ >= 6) {
		print "<input type=hidden name=oldname ",
		      "value=\"$rec{'name'}\">\n";
		print "<input type=hidden name=oldvalue0 value=\"$v[0]\">\n";
		}
	}
elsif ($type eq "SRV") {
	print "<tr> <td><b>$text{'value_SRV1'}</b></td>\n";
	print "<td><input name=value0 size=8 value='$v[0]'></td>\n";

	print "<td><b>$text{'value_SRV2'}</b></td>\n";
	print "<td><input name=value1 size=8 value='$v[1]'></td> </tr>\n";

	print "<tr> <td><b>$text{'value_SRV3'}</b></td>\n";
	print "<td><input name=value2 size=8 value='$v[2]'></td>\n";

	print "<td><b>$text{'value_SRV4'}</b></td>\n";
	print "<td><input name=value3 size=30 value='$v[3]'></td> </tr>\n";
	}
elsif ($type eq "LOC") {
	print "<tr> <td><b>$text{'value_LOC1'}</b></td>\n";
	printf "<td colspan=3><input name=value0 size=45 value=\"%s\"></td> </tr>\n", join(" ", @v);
	}
if ($type ne "WKS") {
	printf "<tr> <td><b>%s</b></td>\n",
		$config{'allow_comments'} ? $text{'edit_comment'} : "";
	printf "<td><input %s name=comment size=30 value=\"%s\"></td> </tr>\n",
		$config{'allow_comments'} ? "" : "type=hidden", $rec{'comment'};
	}
if ($type eq "A" || $type eq "AAAA") {
	print "<tr> <td><b>$text{'edit_uprev'}</b></td>\n";
	printf "<td><input type=radio name=rev value=1 %s> $text{'yes'}\n",
		$config{'rev_def'} == 0 ? 'checked' : "";
	if (@_ < 6) {
		printf "<input type=radio name=rev value=2 %s> %s\n",
			$config{'rev_def'} == 2 ? 'checked' : '',
			$text{'edit_over'};
		}
	printf "<input type=radio name=rev value=0 %s> $text{'no'}</td>\n",
		$config{'rev_def'} == 1 ? 'checked' : '';
	}
elsif ($type eq "PTR") {
	print "<tr> <td><b>$text{'edit_upfwd'}</b></td>\n";
	printf "<td><input type=radio name=fwd value=1 %s> $text{'yes'}\n",
		$config{'rev_def'} ? '' : 'checked';
	printf "<input type=radio name=fwd value=0 %s> $text{'no'}</td>\n",
		$config{'rev_def'} ? 'checked' : '';
	}
else { print "<tr> <td colspan=2></td>\n"; }
print "<td colspan=2 align=right>\n";
local %a = &get_module_acl();
if (!$a{'ro'}) {
	if (@_ >= 6) {
		print "<input type=submit value=\"$text{'save'}\">\n";
		print "<input type=submit name=delete ",
		      "value=\"$text{'delete'}\">\n";
		}
	else { print "<input type=submit value=\"$text{'create'}\">\n"; }
	}
print "</td></tr></table></td></tr></table></form>\n";
}

sub zones_table
{
local($i);
print "<table border width=100%>\n";
print "<tr $tb> <td><b>$text{'index_zone'}</b></td> ",
      "<td><b>$text{'index_type'}</b></td> </tr>\n";
for($i=0; $i<@{$_[0]}; $i++) {
	print "<tr $cb>\n";
	print "<td><a href=\"$_[0]->[$i]\">$_[1]->[$i]</a></td>\n";
	print "<td>$_[2]->[$i]</td>\n";
	print "</tr>\n";
	}
print "</table>\n";
}

# convert_illegal(text)
# Convert text containing special HTML characters to properly display it.
sub convert_illegal
{
$_[0] =~ s/&/&amp;/g;
$_[0] =~ s/>/&gt;/g;
$_[0] =~ s/</&lt;/g;
$_[0] =~ s/"/&quot;/g;
$_[0] =~ s/ /&nbsp;/g;
return $_[0];
}

sub check_net_ip
{
local($j, $arg = $_[0]);
if ($arg !~ /^(\d{1,3}\.){0,3}([0-9\-\/]+)$/) {
	return 0;
	}
foreach $j (split(/\./, $arg)) {
	$j =~ /^(\d+)-(\d+)$/ && $1 < 255 && $2 < 255 ||
	$j =~ /^(\d+)\/(\d+)$/ && $1 < 255 && $2 <= 32 ||
		$j <= 255 || return 0;
	}
return 1;
}

# expand_ip6(ip)
# Transform compact (with ::) IPv6 address to the unique expanded form
# (without :: and leading zeroes in all parts) 
sub expand_ip6
{
local($n);
for($n = 6 - ($_[0] =~ s/([^:]):(?=[^:])/$1:/g); $n > 0; $n--) {
	$_[0] =~ s/::/:0::/;
	}
$_[0] =~ s/::/:/;
$_[0] =~ s/^:/0:/;
$_[0] =~ s/:$/:0/;
$_[0] =~ s/(:|^)0(?=\w)/$1/;
$_[0] =~ tr/[A-Z]/[a-z]/;
return $_[0];
}

# expandall_ip6(ip)
# Transform IPv6 address to the expanded form containing all internal 0's 
sub expandall_ip6
{
&expand_ip6($_[0]);
$_[0] =~ s/(:|^)(\w{3})(?=:|$)/:0$2/g;
$_[0] =~ s/(:|^)(\w{2})(?=:|$)/:00$2/g;
$_[0] =~ s/(:|^)(\w)(?=:|$)/:000$2/g;
return $_[0];
}

# check_ip6address(ip)
# Check if some IPv6 address is properly formatted
sub check_ip6address
{
local($ip6);
$ip6 = $_[0];
$ip6 = &expand_ip6($ip6);
return ($ip6 =~ /^([\da-f]{1,4}:){7}([\da-f]{1,4})$/i);
}

sub time_unit_choice 
{
print "<select name=$_[0]>\n";
printf "<option %s value=\"\">$text{'seconds'}\n",
       $_[1] =~ /^(S?)$/i ? "selected" : "";
printf "<option %s value=\"M\">$text{'minutes'}\n",
       $_[1] =~ /M/i ? "selected" : "";
printf "<option %s value=\"H\">$text{'hours'}\n",
       $_[1] =~ /H/i ? "selected" : "";
printf "<option %s value=\"D\">$text{'days'}\n",
       $_[1] =~ /D/i ? "selected" : "";
printf "<option %s value=\"W\">$text{'weeks'}\n",
       $_[1] =~ /W/i ? "selected" : "";
print "</select>\n";
}

sub extract_time_units
{
local(@ret);
foreach $j (@_) {
	if ($j =~ /^(\d+)([SMHDW]?)$/is) {
		push(@ret, $2); $j = $1;
		}
	}
return @ret;
}

sub email_to_dotted
{
local $v = $_[0];
$v =~ s/\.$//;
if ($v =~ /^([^.]+)\@(.*)$/) {
	return "$1.$2.";
	}
elsif ($v =~ /^(.*)\@(.*)$/) {
	local ($u, $d) = ($1, $2);
	$u =~ s/\./\\\./g;
	return "\"$u.$d.\"";
	}
else {
	return $v;
	}
}

sub dotted_to_email
{
local $v = $_[0];
if ($v ne ".") {
	$v =~ s/([^\\])\./$1\@/;
	$v =~ s/\\\./\./g;
	$v =~ s/\.$//;
	}
return $v;
}

# set_ownership(file)
sub set_ownership
{
if ($config{'file_owner'}) {
	&system_logged("chown \"$config{'file_owner'}\" $_[0] >/dev/null 2>&1");
	}
if ($config{'file_perms'}) {
	&system_logged("chmod \"$config{'file_perms'}\" $_[0] >/dev/null 2>&1");
	}
}

@cat_list = ( 'default', 'config', 'parser', 'queries',
	      'lame-servers', 'statistics', 'panic', 'update',
	      'ncache', 'xfer-in', 'xfer-out', 'db',
	      'eventlib', 'packet', 'notify', 'cname', 'security',
	      'os', 'insist', 'maintenance', 'load', 'response-checks' );

@syslog_levels = ( 'kern', 'user', 'mail', 'daemon', 'auth', 'syslog',
		   'lpr', 'news', 'uucp', 'cron', 'authpriv', 'ftp',
		   'local0', 'local1', 'local2', 'local3',
		   'local4', 'local5', 'local6', 'local7' );

@severities = ( 'critical', 'error', 'warning', 'notice', 'info',
		'debug', 'dynamic' );

