#!/usr/bin/perl
#
#    Originally written by Paul Albitz and Ken Stone of Hewlett Packard
#    h2n-hp,v 1.96 1999/12/02 22:05:56 milli (Michael Milligan), Hewlett-Packard
#    Extended to v 2.56 2004-03-31 by Andris Kalnozols, HP Labs
#
# NAME
#
#    h2n - Translate host table to name server file format
#
# SYNOPSIS
#
#    h2n -d DOMAIN -n NET -u CONTACT [options]
#

use Cwd;		# for calling `getcwd'
use FileHandle;		# for calling `autoflush'
use Symbol;		# for calling `qualify'
use Sys::Hostname;	# for calling `hostname'

# Various defaults
#
my $VERSION = "2.56";
my $Program = $0;
   $Program =~ s/.*\///;
my $Host = hostname();
my $do_aliases = 1;
my $do_mx = 1;
my $do_zone_apex_mx = 0;
my $do_wks = 0;
my $do_txt = 0;
my $do_rp = 0;
my $Bootfile = "./named.boot";
my $Conffile = "./named.conf";
my $new_fmt_Conffile = 0;
my $Conf_prefile = "";
my $Hostfile = "/etc/hosts";
my $Commentfile = "";
my $Pwd = getcwd();
my $Bwd = "";
my $Domain = "";
my $Domainfile = "";
my $RespHost = "";
my $RespUser = "";
my $DefSerial = 1;
my $DefRefresh = "3H";
my $DefRetry = "1H";
my $DefExpire = "1W";
my $DefNegCache = "10M";
my $DefTtl = "1D";
my $need_numeric_ttl = 0;
my $DefMxWeight = 10;
my $Defsubnetmask = "255.255.255.0";
my $Supernetting_enabled = 0;
my $ForceSerial = -1;
my $UseDateInSerial = 0;
my $DateSerial = 0;
my $Print_sequence = 0;
my $Open_DB_files = 0;
my $Open_file_limit = 120;
my $verbose = 1;
my $line_num = 0;
my $show_host_line = 0;
my $gen_count = 0;
my $CustomLogging = 0;
my $CustomOptions = 0;
my $NeedHints = 1;
my $multi_homed_mode = "";
my $check_level = "audit";
my $rfc952 = 0;		   # NOTE: Don't set true unless $rfc1123 is also true.
my $rfc1123 = 1;
my $rfc2308 = 1;	   # New default as of version 2.40
my $rfc2782 = 0;
my $audit = 1;		   # DNS auditing is independent of RFC name checking.
my $DefAction = "Warning"; # Default action to take if hostnames aren't perfect.
my $Load_Status = 0;	   # Set true by READ_RRs() if BIND won't load the zone.
my $newline_printed = 0;
my $preserve_case = 0;
my $verify_mode = 0;
my $recursive_verify = 0;
my $verify_delegations = 1;
my $show_chained_cnames = 0;
my $debug = 0;
my $debug_DIR = "/tmp";
my $valid_SOA_timers = 1;
my $localhost = "127.0.0.1";
my $MakeLoopbackSOA = 1;
my $MakeLoopbackZone = 1;
my $nameset = "";
my $rcfile = "";

# The following two data items are used in verify mode to create a sorted
# list of best possible name servers from which a zone transfer is requested.
# This helps to avoid unnecessary delays by trying name servers on local
# networks first.
#
# * The "@local_networks" array should be initialized to the list of
#   network(s) (in CIDR format) to which the localhost is connected,
#   e.g., a dual-homed localhost should have two entries in this array.
# * The "@local_subnetmasks" array should be initialized to the subnet
#   mask(s) which correspond to the network(s) in "@local_networks".
#
my @local_networks = ("0/0");			# Set default network and mask
my @local_subnetmasks = ("0.0.0.0");		# for universal connectivity.

# The `h2n' program calls two external utilities, `DiG' and `check_del',
# to make various DNS queries.  The following two variables hold their
# filenames.  If not qualified with a pathname, the filename is expected
# to be found in the environment's search path, e.g., if `h2n' is run
# via cron(1M), the default PATH is usually "/usr/bin:/usr/sbin:.".
#
my $DiG = "/usr/local/bin/dig";
my $check_del = "/usr/local/bin/check_del";

# The DiG utility is rather patient in the time it will wait for a
# response before giving up on a name server in an NS RRset (4 seconds
# for versions 2-8.X and 5 seconds for 9.X versions).  It's also
# rather generous in the number of times each name server is retried
# (4 times for 2-8.X versions and 3 times for 9.X versions).
# There is a potential for significant delays when auditing domain
# names that point to unreachable name servers.  The following two
# variables allow these DiG parameters to be customized.
#
my $DiG_timeout = 4;
my $DiG_retries = 2;

# The above six data items that were just initialized can be
# overridden by a special configuration file that the READ_RCFILE
# subroutine will look for a bit later.

# To increase the processing speed of the READ_RRs subroutine, arrange the
# set of recognized RR types in order of decreasing frequency, i.e.,
# the most common types of RRs should appear first.  There can be up to
# a 7% speed difference between best-case and worst-case ordering.
# NOTE: * Per RFC-1035, the NULL RR is not allowed in master files and,
#         thus, the NULL RR is deliberately not recognized.
#       * Per RFC-1123, the obsolete MD and MF RR types are deliberately
#         not recognized.
#       * The meta-RRs OPT (RFC-2671), TSIG (RFC-2845), and TKEY (RFC-2930)
#         are recognized in case they appear in the output of utilities
#         such as DiG which this program may use.
#
my $RRtypes = "MX|A|CNAME|PTR|NS|AAAA|HINFO|RP|TXT|SRV|SOA|A6|M[BGR]|"
	    . "MINFO|NSAP|NSAP-PTR|AFSDB|RT|ISDN|X25|PX|NAPTR|LOC|CERT|"
	    . "SIG|KEY|NXT|KX|DNAME|DS|WKS|EID|NIMLOC|ATMA|GPOS|APL|SINK|"
	    . "TSIG|OPT|TKEY";

# Construct a table of BIND versions that are vulnerable to various bugs
# according to < http://www.isc.org/products/BIND/bind-security.html >.
# A warning will be displayed anytime `h2n' detects one of these versions.
# NOTE: The GET_BIND_VERSION() subroutine will also determine if any
#       of the following bugs should be reported as possibly present:
#
#         DoS internal consistency check
#         libbind buffer overflow
#         OpenSSL buffer overflow
#         BIND: Remote Execution of Code
#         BIND: Multiple Denial of Service
#         LIBRESOLV: buffer overrun
#
my %BIND_bug_index = (
 '4.8'	   => 'infoleak',
 '4.8.1'   => 'infoleak',
 '4.8.2.1' => 'infoleak',
 '4.8.3'   => 'infoleak',
 '4.9.3'   => 'complain infoleak',
 '4.9.4'   => 'complain infoleak',
 '4.9.4-P1'=> 'complain infoleak',
 '4.9.5'   => 'sig naptr maxdname complain infoleak',
 '4.9.5-P1'=> 'sig naptr maxdname complain infoleak',
 '4.9.6'   => 'sig naptr maxdname complain infoleak',
 '4.9.7'   => 'naptr maxdname complain infoleak',
 '4.9.8'   => 'naptr maxdname',
 '8.1'	   => 'sig naptr maxdname solinger fdmax infoleak',
 '8.1.1'   => 'sig naptr maxdname solinger fdmax infoleak',
 '8.1.2'   => 'naptr maxdname solinger fdmax infoleak',
 '8.2'	   => 'sigdiv0 srv nxt sig naptr maxdname solinger fdmax infoleak tsig',
 '8.2-P1'  => 'sigdiv0 srv nxt sig naptr maxdname solinger fdmax infoleak tsig',
 '8.2.1'   => 'sigdiv0 srv nxt sig naptr maxdname solinger fdmax infoleak tsig',
 '8.2.2'   => 'zxfr sigdiv0 srv naptr maxdname infoleak tsig',
 '8.2.2-P1'=> 'zxfr sigdiv0 srv naptr maxdname infoleak tsig',
 '8.2.2-P2'=> 'zxfr sigdiv0 srv infoleak tsig',
 '8.2.2-P3'=> 'zxfr sigdiv0 srv infoleak tsig',
 '8.2.2-P4'=> 'zxfr sigdiv0 srv infoleak tsig',
 '8.2.2-P5'=> 'zxfr sigdiv0 srv infoleak tsig',
 '8.2.2-P6'=> 'zxfr srv infoleak tsig',
 '8.2.2-P7'=> 'infoleak tsig',
 '8.2.3-T' => 'tsig'
);

# URLs that document the above-mentioned BIND bugs
#
my $CERT_URL_bugs    = "< http://www.cert.org/advisories/CA-2001-02.html >";
my $CERT_URL_DoS     = "< http://www.cert.org/advisories/CA-2002-15.html >";
my $CERT_URL_libbind = "< http://www.cert.org/advisories/CA-2002-19.html >";
my $CERT_URL_openssl = "< http://www.cert.org/advisories/CA-2002-23.html >";
my $CERT_URL_buf_DoS = "< http://www.cert.org/advisories/CA-2002-31.html >";
my $ISC_URL = "< http://www.isc.org/products/BIND/bind-security.html >";

my ($BIND_ver_msg, $BIND_version, $BIND_version_num, $Bootsecaddr);
my ($Bootsecsaveaddr, $Confsecaddr, $Confsecsaveaddr, $DiG_bufsize);
my ($DiG_version_num, $Domainpattern, $Expire, $MasterTtl, $NewSerial);
my ($Refresh, $Retry, $SOA_count, $Search_dir, $Serial, $Specialfile, $Ttl);
my ($UseDefaultDomain, $User);
my ($action, $addr, $addrpattern, $alias, $cDomain, $canonical, $cmode);
my ($comment, $common_alias, $continue_search, $data, $data_fname);
my ($debug_BIND_version, $default_PTR, $error, $first_IP, $ignore_c_opt, $key);
my ($last_IP, $last_octet, $make_rr, $match, $message, $names, $netpat);
my ($pDomain, $pmode, $ptr_file, $ptr_owner, $ptr_template, $ptrpat);
my ($reformatted_line, $show_nxdomain_cnames, $soa_warned, $tmp, $tmp_rfc952);
my ($ttl, $uqdn, $zone_file, $zone_name);
my (%Aliases, %AliasesPTR, %Apex_RRs, %Apex_aliases, %Apex_route_RRs);
my (%CommentRRs, %Comments, %DB_filehandle, %Hosts, %HostsPTR, %LRUtable);
my (%MasterZoneOptions, %MXlist, %NSowners, %NSlist, %Net_ranges, %Net_zones);
my (%PartialServers, %RRowners, %SlaveZoneOptions, %Ttl, %Wildcards, %cAliases);
my (%cModeSpec, %cpatrel, %deferredPTR, %ePatExceptions, %extNS, %pModeSpec);
my (%pPTR, %pendingPTR, %ptrpatrel, %spclAFSDB, %spclCNAME, %spclPTR, %spclRP);
my (%spclRT, %spclSRV);
my (@BootOptions, @ConfLogging, @ConfOptions, @DNSrrset, @FullServers);
my (@GlobalMasterZoneOptions, @GlobalSlaveZoneOptions, @MX, @Vdomains);
my (@addrs, @aliases, @bootmsgs, @cpats, @elimpats, @makesoa, @our_addrs);
my (@our_netbits, @our_nets, @our_subnetmasks, @our_subnets, @ptrpats);

autoflush STDOUT 1;		# Allows single-character output w/o newline.
autoflush STDERR 1;		# Keeps STDERR output synchronized with STDOUT.

&READ_RCFILE;			# a way to override various built-in defaults
&PARSE_ARGS(@ARGV);		# overrides any options found in a CONF file
&FIXUP;

if ($DiG_version_num == 90000 && ($verify_mode || $audit)) {
    ($message = <<EOT) =~ s/^\s+\|//gm;
    |
    |The DiG utility found by `h2n' is version 9.0. It is unsupported
    |due to its inability to echo batch file input.  Please replace it
    |with another version of DiG from either BIND 8 or the most recent
    |distribution of BIND 9.
EOT
    unless ($verify_mode) {
	$message .= "The auditing option has been disabled for this run.\n";
	$audit = 0;
    }
    print STDERR "$message\n";
    exit(2) if $verify_mode;
}
# NOTE: All versions of DiG 9.0.1, while usable by `h2n', have a small
#       defect whereby the command line is not echoed whenever any kind of
#       name server connection failure is encountered.  The AUDIT_RRs()
#       subroutine treats these detected cases as a general synchronization
#       failure when parsing the output from DiG and will generate a status
#       of "[sync. error ]" for the queried domain name.
#

if ($verify_mode) {		# If called with -V
    &VERIFY_ZONE;
    exit 0;
}

if (!$rfc2308 || $BIND_version_num < 80201) {
    #
    # When in doubt, convert time intervals that may be in symbolic
    # notation to the equivalent number of seconds.  This is the
    # universally understood format.
    #
    $need_numeric_ttl = 1;
    $DefRefresh	 = &SECONDS($DefRefresh);
    $DefRetry	 = &SECONDS($DefRetry);
    $DefExpire	 = &SECONDS($DefExpire);
    $DefNegCache = &SECONDS($DefNegCache);
    $DefTtl	 = &SECONDS($DefTtl);
    $Refresh	 = &SECONDS($Refresh) if $Refresh;
    $Retry	 = &SECONDS($Retry) if $Retry;
    $Expire	 = &SECONDS($Expire) if $Expire;
    $Ttl	 = &SECONDS($Ttl) if $Ttl;
    $MasterTtl	 = &SECONDS($MasterTtl) if $MasterTtl;
}

if ($BIND_ver_msg) {
    ($tmp = $RespHost) =~ s/\.$//;
    $BIND_ver_msg =~ s/ See /See /;
    $BIND_ver_msg =~ s/     </    </g;
    print STDERR "\nWarning: $tmp (-h option) is running BIND $BIND_version.\n";
    print STDERR "This version of BIND may be vulnerable to the following bug(s):\n";
    print STDERR "$BIND_ver_msg\n\n";
}

print STDOUT "Initializing new database files...\n" if $verbose;
&INITDBs;

if (-r $Specialfile) {
    print STDOUT "Reading special file `$Specialfile'...\n" if $verbose;
    #
    # Make sure to disable checking of single-character hostnames and
    # aliases since this RFC-952 restriction is limited to host files.
    #
    $tmp_rfc952 = $rfc952;
    $rfc952 = 0;

    # The "$newline_printed" variable controls the printing of cosmetic
    # newlines surrounding a block of warning messages that the
    # READ_RRs subroutine may generate.  "$newline_printed" is global
    # in scope to prevent unwanted extra newlines in case READ_RRs is
    # called recursively.  If necessary, the initial newline is output
    # within READ_RRs while the one or two terminating newline characters
    # are printed after the subroutine returns to its original caller.
    #
    $newline_printed = &READ_RRs($Specialfile, "$Domain.", "$Domain.",
				 "$Domain.", 0);
    if ($verbose) {
	print STDERR "\n" while $newline_printed--;
    } else {
	$newline_printed = 0;
    }
    $rfc952 = $tmp_rfc952;
}

print STDOUT "Reading host file `$Hostfile'...\n" if $verbose;
unless (open(*HOSTS, $Hostfile)) {
    print STDERR "Couldn't open the host file: $!\n";
    print STDERR "Check your -H option argument.\n";
    print STDERR "I give up ... sorry.\n";
    exit(2);
}
LINE: while (<HOSTS>) {
    $line_num++;
    chop;					# Remove the trailing newline.
    next if /^#/ || /^$/;			# Skip comments and empty lines.
    ($data, $comment) = split('#', $_, 2);	# Separate comments from the
    $comment = "" unless defined($comment);	# interesting bits.
    ($addr, $names) = split(' ', $data, 2);	# Isolate IP addr. from name(s).
    if ($addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
	if (($4 > 255 || $3 > 255 || $2 > 255 || $1 > 255)
	    || $addr =~ /(^|\.)0\d/) {
	    $message = 1;
	} else {
	    $message = 0;
	}
    } else {
	$message = 1;
    }
    if ($message || $names =~ /^\s*$/) {
	if ($verbose) {
	    print STDERR "Line $line_num: Skipping; incorrectly formatted data.\n";
	    print STDERR "> $_\n";
	}
	next LINE;
    }
    $names = lc($names) unless $preserve_case;

    # Match -e args
    #
    foreach $netpat (@elimpats) {
	if ($names =~ /[.\s]$netpat/i) {
	    #
	    # If a -e domain happens to be a parent of the -d domain or
	    # a -c or -p domain, exit this loop and continue processing.
	    # Otherwise, fetch the next host file entry.
	    #
	    last if $names =~ /$ePatExceptions{$netpat}/i;
	    next LINE;
	}
    }

    if ($comment =~ /\[\s*ttl\s*=\s*(\d+|(\d+[wdhms])+)\s*\]/i) {
	$ttl = $1;				# set TTL if explicit
	if ($need_numeric_ttl) {		# and convert time
	    $ttl = &SECONDS($ttl);		# format if necessary
	} else {
	    $ttl = &SYMBOLIC_TIME($ttl);
	}
    } else {
	$ttl = "";				# set TTL to implicit default
    }
    # Separate the canonical name from any aliases that follow.
    #
    ($canonical, @aliases) = split(' ', $names);
    $error = &CHECK_NAME($canonical, 'A');
    if ($error) {
	$action = ($error == 3) ? "Skipping" : $DefAction;
	if ($verbose) {
	    print STDERR "Line $line_num: $action; `$canonical' not a valid canonical hostname.\n";
	    if ($action eq "Skipping") {
		print STDERR "> $_\n";
	    } else {
		$show_host_line = 1;
	    }
	}
	next LINE if $action eq "Skipping";
    }
    $reformatted_line = 0;

    # Process -c arguments
    #
    # If a `spcl' file exists for the `-d' domain, it has already been read.
    # The %RRowners hash now contains the discovered `spcl' and -T option
    # domain names and will be used to prevent the creation of conflicting
    # CNAMEs.
    # Checks are also made to prevent the accidental creation of cross-domain
    # wildcard CNAMEs in case "*" characters are lurking in the host table.
    # NOTE: The "@cpats" array has been sorted by the FIXUP() subroutine so
    #       that the deepest subtrees of each domain will be matched before
    #       their ancestors in the domain tree.
    #
    $cmode = "";
    foreach $netpat (@cpats) {
	if ($names =~ /\.$netpat/i) {
	    next LINE if $addr eq $localhost;
	    $cmode = $cModeSpec{$netpat};
	    if ($cmode =~ /O/) {
		#
		# The matched domain is a parent domain of the default
		# domain (-d option).  Make sure to override -c option
		# processing if the current host file entry also matches
		# the -d option domain.
		#
		if ($names =~ /$Domainpattern/i) {
		    $cmode = "";
		    last;
		}
	    }
	    $cDomain = $cpatrel{$netpat};
	    #
	    # Although the fully-qualified canonical host name is
	    # supposed to follow the IP address in a properly
	    # formatted host table, some sysadmins use the following
	    # style instead:
	    #
	    #   192.168.0.1   host1  host1.movie.edu  alias1  alias2 ...
	    #
	    # We'll accommodate this idiosyncrasy by comparing the
	    # canonical host against the first alias.  If the only
	    # difference is the default domain name, the name columns
	    # will be shifted to eliminate the redundancy.
	    # Another common formatting style is to have a fully-qualified
	    # canonical host name followed by the unqualified host name as
	    # the first alias.  If this is the case, the redundant alias
	    # will be eliminated.
	    #
	    if (@aliases) {
		$data = ($cmode =~ /I/) ? $Domain : $cDomain;
		if (lc("$canonical.$data") eq lc($aliases[0])) {
		    $canonical = $aliases[0];
		    shift(@aliases);
		    $reformatted_line = 1;
		}
		if (@aliases && lc($canonical) eq lc("$aliases[0].$data")) {
		    shift(@aliases);
		    $reformatted_line = 1;
		}
	    }
	    ($uqdn = $canonical) =~ s/\.$netpat//i;
	    $ignore_c_opt = 0;
	    if ($comment =~ /\[\s*(no|ignore)\s*-c\s*\]/i) {
		$ignore_c_opt = 1;
	    } elsif ($cmode !~ /D/) {
		#
		# Create CNAMEs now, not later.
		#
		if ($cmode =~ /I/) {
		    #
		    # Make the intra-zone domain name in the CNAME's RDATA
		    # field relative to the zone's origin (-d option).
		    #
		    $cDomain =~ s/$Domainpattern$//;
		} else {
		    #
		    # Make the RDATA domain name absolute.
		    #
		    $cDomain .= ".";
		}
		if (exists($RRowners{$uqdn})) {
		    #
		    # Accommodate any DNSSEC-related RRs from a `spcl'
		    # file that are allowed to co-exist with CNAMEs
		    # per RFC-2065.
		    #
		    $match = $RRowners{$uqdn};
		    1 while $match =~ s/ (SIG|NXT|KEY) / /;
		} else {
		    $match = " ";
		}
		if ($match eq " " && $uqdn !~ /^\*($|\.)/) {
		    &PRINTF(*DOMAIN, "%s%s\tCNAME\t%s.%s\n", &TAB($uqdn, 16), $ttl, $uqdn, $cDomain);
		    if (exists($RRowners{$uqdn})) {
			$RRowners{$uqdn} .= "CNAME ";
		    } else {
			$RRowners{$uqdn} = " CNAME ";
		    }
		    $data = $uqdn;
		    while ($data =~ /(\\[.]|[^.])*\./) {
			#
			# The UQDN consists of two or more labels.
			# Register the interior labels in the %RRowners hash
			# so that we can correctly distinguish between a
			# non-existent domain name and a domain name with no
			# DNS resource records during the auditing phase.
			#
			$data =~ s/(\\[.]|[^.])*\.//;	# strip leading label
			$RRowners{$data} = " " unless exists($RRowners{$data});
		    }
		} elsif ($match ne " " && $verbose) {
		    print STDERR "Line $line_num: Can't create CNAME for `$uqdn'; another RR exists.\n";
		    $show_host_line = 1;
		}
		if ($cmode =~ /A/) {	    # also create CNAMEs for alias(es)
		    foreach $tmp (@aliases) {
			($alias = $tmp) =~ s/\.$netpat//i;
			$make_rr = 1 unless $alias =~ /^\*($|\.)/;
			$error = &CHECK_NAME($alias, 'CNAME');
			if ($error) {
			    $action = ($error == 3) ? "Skipping" : $DefAction;
			    if ($verbose) {
				print STDERR "Line $line_num: $action; `$alias' not a valid hostname alias.\n";
				$show_host_line = 1;
			    }
			    $make_rr = 0 unless $action eq "Warning";
			}
			if ($make_rr) {
			    if (exists($RRowners{$alias})) {
				$match = $RRowners{$alias};
				1 while $match =~ s/ (SIG|NXT|KEY) / /;
			    } else {
				$match = " ";
			    }
			    if ($match eq " ") {
				&PRINTF(*DOMAIN, "%s%s\tCNAME\t%s.%s\n", &TAB($alias, 16), $ttl, $uqdn, $cDomain);
				if (exists($RRowners{$alias})) {
				    $RRowners{$alias} .= "CNAME ";
				} else {
				    $RRowners{$alias} = " CNAME ";
				}
				$data = $alias;
				while ($data =~ /(\\[.]|[^.])*\./) {
				    $data =~ s/(\\[.]|[^.])*\.//;
				    unless (exists($RRowners{$data})) {
					$RRowners{$data} = " ";
				    }
				}
			    } elsif ($verbose) {
				print STDERR "Line $line_num: Can't create CNAME for `$alias'; another RR exists.\n";
				$show_host_line = 1;
			    }
			}
		    }
		}
		    
	    } else {
		#
		# Defer creation of CNAMEs
		#
		if (exists($RRowners{$uqdn})) {
		    $match = $RRowners{$uqdn};
		    1 while $match =~ s/ (SIG|NXT|KEY) / /;
		} else {
		    $match = " ";
		}
		if ($match eq " " && $uqdn !~ /^\*($|\.)/) {
		    $cAliases{$uqdn} = "$uqdn $netpat $ttl";
		} elsif ($match ne " " && $cmode !~ /Q/ && $verbose) {
		    print STDERR "Line $line_num: Can't create CNAME for `$uqdn'; another RR exists.\n";
		    $show_host_line = 1;
		}
		if ($cmode =~ /A/) {
		    foreach $tmp (@aliases) {
			($alias = $tmp) =~ s/\.$netpat//i;
			$make_rr = 1 unless $alias =~ /^\*($|\.)/;
			$error = &CHECK_NAME($alias, 'CNAME');
			if ($error) {
			    $action = ($error == 3) ? "Skipping" : $DefAction;
			    if ($verbose) {
				print STDERR "Line $line_num: $action; `$alias' not a valid hostname alias.\n";
				$show_host_line = 1;
			    }
			    $make_rr = 0 unless $action eq "Warning";
			}
			if ($make_rr) {
			    if (exists($RRowners{$alias})) {
				$match = $RRowners{$alias};
				1 while $match =~ s/ (SIG|NXT|KEY) / /;
			    } else {
				$match = " ";
			    }
			    if ($match eq " ") {
				$cAliases{$alias} = "$uqdn $netpat $ttl";
			    } elsif ($cmode !~ /Q/ && $verbose) {
				print STDERR "Line $line_num: Can't create CNAME for `$alias'; another RR exists.\n";
				$show_host_line = 1;
			    }
			}
		    }
		}
	    }
	    # Check if this was flagged as an intra-zone domain or
	    # if there is a matching -p argument.  A check made in
	    # PARSE_ARGS() has ensured that both conditions can not
	    # be simultaneously true.
	    #
	    if ($cmode =~ /I/) {
		$match = 1;
	    } else {
		$match = 0;
		foreach $ptrpat (@ptrpats) {
		    if ($names =~ /\.$ptrpat/i) {
			$match = 1;
			last;
		    }
		}
	    }
	    unless ($match) {
		if ($show_host_line) {
		    print STDERR "> $_\n";
		    $show_host_line = 0;
		}
		next LINE;
	    } else {
		last;		# exit from the -c processing loop
	    }
	}
    }

    # Check that the address is covered by a -n or -a option.
    # Search in the order of most specific network class to
    # the least specific network class, i.e.,
    #
    #   1. sub class-C (/25 to /32)
    #   2. class C     (/17 to /24)
    #   3. class B     (/9  to /16)
    #   3. class A     (/8)
    #
    $continue_search = 1;
    ($data = $addr) =~ s/[.]\d+$//;
    if (exists($Net_ranges{"$data.X"})) {
	$netpat = $Net_ranges{"$data.X"};
	#
	# The "$netpat" variable contains a list of IP address ranges or
	# individual IP addresses that correspond to one or more /25 to
	# /32 networks having the /24 network in "$data" as the common
	# class-C parent network.  Further searching is necessary to
	# determine if there's a match with the last octet of current
	# host entry's IP address.
	#
	$match = 0;
	($last_octet = $addr) =~ s/^.+[.]//;
	foreach $tmp (split(' ', $netpat)) {
	    ($first_IP, $last_IP) = split(/-/, $tmp);
	    $last_IP = $first_IP unless defined($last_IP);
	    if ($last_octet >= $first_IP && $last_octet <= $last_IP) { 
		$ptr_file = $Net_ranges{"$data.$tmp"};
		$match = 1;
		last;
	    }
	}
	$continue_search = 0 if $match || !$Supernetting_enabled;
    }
    if ($continue_search) {
	#
	# When finding a match for a class C, B, or A network,
	# the "$ptr_file" variable can either be the null string
	# (signifying a -a option) or it can be a file descriptor
	# typeglob having "*" as the first character (-n option).
	#
	if (exists($Net_ranges{$data})) {
	    #
	    # A `-a/-n network/17-24' option was matched.
	    #
	    $ptr_file = $Net_ranges{$data};
	    $match = 1;
	} else {
	    #
	    # Remove another octet to see if the address matches
	    # a `-a/-n network/9-16' option, i.e., a class-B zone.
	    #
	    $data =~ s/[.]\d+$//;
	    if (exists($Net_ranges{$data})) {
		$ptr_file = $Net_ranges{$data};
		$match = 1;
	    } else {
		#
		# Remove another octet to see if the address matches
		# a `-a/-n network/8' option, i.e., a class-A zone.
		#
		$data =~ s/[.]\d+$//;
		if (exists($Net_ranges{$data})) {
		    $ptr_file = $Net_ranges{$data};
		    $match = 1;
		} else {
		    $match = 0;
		}
	    }
	}
    }
    unless ($match) {
	if ($verbose) {
	    print STDERR "Line $line_num: Skipping; IP not within range specified by -n/-a options.\n";
	    if ($cmode =~ /I/ && !$ignore_c_opt) {
		print STDERR "Dangling CNAME(s) may exist in the -d domain due to prior -c option processing.\n";
	    }
	    print STDERR "> $_\n";
	    $show_host_line = 0;
	}
	next LINE;
    }

    if ($ptr_file) {
	#
	# A -n option has been matched.  Separate the file handle for
	# printing PTR zone data from the IP address template that is
	# used for creating the relative owner name of the PTR record.
	#
	($ptr_file, $ptr_template) = split(' ', $ptr_file, 2);
    }

    # Process -p args
    # NOTE: The "@ptrpats" array has been sorted by the FIXUP() subroutine
    #       so that the deepest subtrees of each domain will be matched
    #       before their ancestors in the domain tree.
    #
    foreach $netpat (@ptrpats) {
	if ($names =~ /\.$netpat/i) {
	    next LINE if !$ptr_file || $addr eq $localhost
			 || $comment =~ /\[\s*no(\s*-*\s*)?ptr\s*\]/i;
	    $pmode = $pModeSpec{$netpat};
	    if ($pmode =~ /O/) {
		#
		# The matched domain is a parent domain of the default
		# domain (-d option).  Make sure to override -p option
		# processing if the current host file entry also matches
		# the -d option domain.
		#
		last if $names =~ /$Domainpattern/i;
	    }
	    #
	    # Accommodate differing host table formats unless this has
	    # already been done by a previously matched -c option.
	    #
	    if (!$reformatted_line && @aliases) {
		$pDomain = $ptrpatrel{$netpat};
		if (lc("$canonical.$pDomain") eq lc($aliases[0])) {
		    $canonical = $aliases[0];
		    shift(@aliases);
		}
		if (@aliases && lc($canonical) eq lc("$aliases[0].$pDomain")) {
		    shift(@aliases);
		}
	    }
	    unless ($canonical =~ /\.$netpat/i) {
		if ($show_host_line) {
		    print STDERR "> $_\n";
		    $show_host_line = 0;
		}
		next LINE;
	    }
	    #
	    # See the comments in the section for processing the -n option
	    # in PARSE_ARGS for an explanation on how the "/ee" modifier
	    # activates "$ptr_template" in the following substitution.
	    #
	    ($ptr_owner = $addr) =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$ptr_template/ee;
	    $canonical .= "." unless $canonical =~ /\.$/;
	    #
	    # This program has a feature for choosing how PTR records
	    # get generated for multi-homed hosts.  They can either
	    # point to the multi-address canonical name (the default)
	    # or, alternatively, to the first unique single-address
	    # interface name.  The default method allows us to create
	    # the PTR record immediately while the alternate method
	    # requires us to defer the PTR creation since it may not
	    # yet be known whether the current host has more than one
	    # address.
	    #
	    # We'll first determine how to set the $default_PTR flag
	    # based on the relevant conditions and exceptions that
	    # may be present with this host.  Creation or deferral
	    # of the PTR record will then follow.
	    #
	    if (!@aliases || $pmode =~ /A/) {
		#
		# No aliases or a "mode=A" argument in the -p option means
		# that there is no other choice but to use the canonical
		# name as the RDATA field of the PTR record.
		# NOTE: The "mode=A" argument overrides the `+m P' and `+m CP'
		#       options as well as the "[mh=p]" and "[mh=cp]" flags in
		#       the comment field of this host.
		#
		$default_PTR = 1;
	    } else {
		#
		# Test the status of any +m option that was specified
		# and whether or not it is being overridden by a "[mh=??]"
		# flag in the comment field.
		#
		unless ($multi_homed_mode =~ /P/) {
		    #
		    # Use the default PTR method unless overridden.
		    #
		    $default_PTR = ($comment =~ /\[\s*mh\s*=\s*(p|cp|pc)\s*\]/i) ? 0 : 1;
		} else {
		    #
		    # Use the alternate PTR method unless overridden.
		    # NOTE: The absence of the "p" specification in the
		    #       comment flag signifies an override condition.
		    #
		    $default_PTR = ($comment =~ /\[\s*mh\s*=\s*[cd]\s*\]/i) ? 1 : 0;
		}
	    }
	    if ($default_PTR) {
		#
		# The PTR record must point to the canonical name
		# and thus can be created immediately.
		#
		unless (exists($pPTR{$addr})) {
		    unless ($ptr_file eq *DOMAIN) {
			&PRINTF($ptr_file, "%s%s\tPTR\t%s\n", &TAB($ptr_owner, 8), $ttl, $canonical);
			$pPTR{$addr} = 1;
		    } elsif (exists($RRowners{$ptr_owner})
			     && $RRowners{$ptr_owner} =~ / CNAME /) {
			if ($verbose) {
			    print STDERR "Line $line_num: Can't create PTR for `$ptr_owner'; a CNAME RR exists.\n";
			    $show_host_line = 1;
			}
		    } else {
			&PRINTF(*DOMAIN, "%s%s\tPTR\t%s\n", &TAB($ptr_owner, 16), $ttl, $canonical);
			$pPTR{$addr} = 1;
			if (exists($RRowners{$ptr_owner})) {
			    $RRowners{$ptr_owner} .= "PTR " unless $RRowners{$ptr_owner} =~ / PTR /;
			} else {
			    $RRowners{$ptr_owner} = " PTR ";
			}
			$data = $ptr_owner;
			while ($data =~ /(\\[.]|[^.])*\./) {
			    $data =~ s/(\\[.]|[^.])*\.//;
			    $RRowners{$data} = " " unless exists($RRowners{$data});
			}
		    }
		}
	    } else {
		#
		# Defer the creation of the PTR record until it can be
		# determined whether or not the canonical name is that
		# of a multi-homed host.  If so, the first non-common
		# alias will have an Address record in the forward-mapping
		# file and the PTR record will created to point to it
		# instead of the canonical name.
		#
		unless (exists($HostsPTR{$canonical})) {
		    ($tmp = $netpat) =~ s/\\././g;
		    $HostsPTR{$canonical} = "$tmp ";
		}
		($addrpattern = $addr) =~ s/\./\\./g;
		unless ($HostsPTR{$canonical} =~ /\b$addrpattern /) {
		    #
		    # Add the new IP address to the hash of canonical names
		    # for PTR records and store the deferred PTR data.
		    #
		    $HostsPTR{$canonical} .= "$addr ";
		    $deferredPTR{"$canonical-$addr"} = "$ptr_file $ptr_owner $ttl";
		}
		# Make sure that all aliases get indexed.
		#
		$AliasesPTR{"$canonical-$addr"} .= "@aliases ";
	    }
	    if ($show_host_line) {
		print STDERR "> $_\n";
		$show_host_line = 0;
	    }
	    next LINE;
	}
    }

    # Accommodate differing host table formats unless this has
    # already been done by a previously matched intra-zone
    # -c option (mode=I).
    #
    if (!$reformatted_line && @aliases) {
	if (lc("$canonical.$Domain") eq lc($aliases[0])) {
	    $canonical = $aliases[0];
	    shift(@aliases);
	}
	if (@aliases && lc($canonical) eq lc("$aliases[0].$Domain")) {
	    shift(@aliases);
	}
    }
    unless ($canonical =~ /$Domainpattern$/i || $UseDefaultDomain) {
	if ($verbose) {
	    print STDERR "Line $line_num: Skipping `$canonical'.\nThe canonical name does not match the -d option.\n";
	    print STDERR "> $_\n";
	    $show_host_line = 0;
	}
	next LINE;
    } else {
	$canonical =~ s/$Domainpattern$//i;	# strip off domain if present
	if (exists($Hosts{$canonical})) {
	    ($addrpattern = $addr) =~ s/\./\\./g;
	    unless ($Hosts{$canonical} =~ /\b$addrpattern /) {
		#
		# The above check prevents the creation of duplicate
		# Address and PTR records.  Now go ahead and index the
		# address by canonical name.
		#
		$Hosts{$canonical} .= "$addr ";
		$addrpattern = "";
	    }
	} else {
	    #
	    # This is the first IP address for this host.
	    #
	    $Hosts{$canonical} = "$addr ";
	    $addrpattern = "";
	    $data = $canonical;
	    while ($data =~ /(\\[.]|[^.])*\./) {
		$data =~ s/(\\[.]|[^.])*\.//;
		$RRowners{$data} = " " unless exists($RRowners{$data});
	    }
	}
	# Index aliases by name and address.
	#
	$Aliases{"$canonical-$addr"} .= "@aliases ";
	$Comments{"$canonical-$addr"} .= $comment;
	if ($ttl) {
	    if (exists($Ttl{$canonical})) {
		if (&SECONDS($ttl) < &SECONDS($Ttl{$canonical})) {
		    $Ttl{$canonical} = $ttl;
		}
		if ($verbose) {
		    print STDERR "Line $line_num: Hmm, another TTL spec for `$canonical', using lowest value ($Ttl{$canonical}).\n";
		    $show_host_line = 1;
		}
	    } else {
		$Ttl{$canonical} = $ttl;
	    }
	}
	if ($ptr_file && $addrpattern eq "" && $addr ne $localhost
	    && $comment !~ /\[\s*no(\s*-*\s*)?ptr\s*\]/i) {
	    #
	    # Construct the data for the PTR record.  See the comments
	    # in the section for processing the -n option in PARSE_ARGS
	    # for an explanation on how the "/ee" modifier activates
	    # "$ptr_template" in the following substitution.
	    #
	    ($ptr_owner = $addr) =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$ptr_template/ee;
	    unless (@aliases && $do_aliases) {
		# No aliases or the -A option being in effect means
		# that there is no other choice but to use the canonical
		# name as the RDATA field of the PTR record.
		# NOTE: The -A option overrides the `+m P' and `+m CP'
		#       options as well as the "[mh=p]" and "[mh=cp]"
		#       flags in the comment field of this host.
		#
		$default_PTR = 1;
	    } else {
		unless ($multi_homed_mode =~ /P/) {
		    $default_PTR = ($comment =~ /\[\s*mh\s*=\s*(p|cp|pc)\s*\]/i) ? 0 : 1;
		} else {
		    $default_PTR = ($comment =~ /\[\s*mh\s*=\s*[cd]\s*\]/i) ? 1 : 0;
		}
	    }
	    if ($default_PTR) {
		unless ($ptr_file eq *DOMAIN) {
		    &PRINTF($ptr_file, "%s%s\tPTR\t%s.%s.\n", &TAB($ptr_owner, 8), $ttl, $canonical, $Domain);
		} elsif (exists($RRowners{$ptr_owner})
			 && $RRowners{$ptr_owner} =~ / CNAME /) {
		    if ($verbose) {
			print STDERR "Line $line_num: Can't create PTR for `$ptr_owner'; a CNAME RR exists.\n";
			$show_host_line = 1;
		    }
		} else {
		    &PRINTF(*DOMAIN, "%s%s\tPTR\t%s\n", &TAB($ptr_owner, 16), $ttl, $canonical);
		    if (exists($RRowners{$ptr_owner})) {
			$RRowners{$ptr_owner} .= "PTR " unless $RRowners{$ptr_owner} =~ / PTR /;
		    } else {
			$RRowners{$ptr_owner} = " PTR ";
		    }
		    $data = $ptr_owner;
		    while ($data =~ /(\\[.]|[^.])*\./) {
			$data =~ s/(\\[.]|[^.])*\.//;
			$RRowners{$data} = " " unless exists($RRowners{$data});
		    }
		}
	    } else {
		$deferredPTR{"$canonical-$addr"} = "$ptr_file $ptr_owner $ttl";
	    }
	}
    }
    if ($show_host_line) {
	print STDERR "> $_\n";
	$show_host_line = 0;
    }
}
close(*HOSTS);

if ($Commentfile) {
    print STDOUT "Reading comments file `$Commentfile'...\n" if $verbose;
    unless (&OPEN(*F, $Commentfile)) {
	print STDERR "Unable to open the comments file': $!\n";
	print STDERR "Check your -C option argument.\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    }
    while (<F>) {
	chop;
	($key, $comment) = split(':', $_, 2);
	$CommentRRs{$key} = $comment;
    }
    &CLOSE(*F);
}

print STDOUT "Writing database files...\n" if $verbose;

# Go through the list of canonical names.
# If there is more than one address associated with a name, it is a
# multi-homed host.  Special checks are made for the generation of
# A and/or CNAME RRs for multi-homed hosts in the CNAME() subroutine.
#
# Since the %Hosts hash may be quite large, do not call the keys()
# function in a list context.  To do so would incur unnecessary
# overhead in both time and memory space when every hash key is
# slurped into the list at once.  Instead, the each() function will
# be used to access the hash keys and elements one by one.
# It's imperative, however, to first call keys() in a scalar context
# in order to reset the internal hash iterator.  Otherwise, data might
# be missed if the each() function doesn't start accessing the hash
# from the beginning.
# 
scalar(keys(%Hosts));
while (($canonical, $data) = each %Hosts) {
    @addrs = split(' ', $data);

    $ttl = (defined($Ttl{$canonical})) ? $Ttl{$canonical} : "";
    foreach $addr (@addrs) {
	#
	# Print address record(s) for the canonical name.
	#
	if (exists($RRowners{$canonical}) && $RRowners{$canonical} =~ / CNAME /) {
	    print STDERR "Can't create A record for `$canonical' due to an existing CNAME RR.\n" if $verbose;
	} elsif ($addr ne $localhost) {
	    &PRINTF(*DOMAIN, "%s%s\tA\t%s\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $addr);
	    $nameset = $canonical;
	}
    }
    if ($do_mx) {
	&MX($canonical, @addrs);
    }
    if ($do_txt) {
	&TXT($canonical, @addrs);
    }
    if ($Commentfile) {
	&DO_COMMENTS($canonical, @addrs);
    }
    if ($do_rp) {
	&RP($canonical, @addrs);
    }
    if ($do_aliases) {
	&CNAME($canonical, @addrs);
    }
    if (exists($cAliases{$canonical})) {
	#
	# RRs for the default domain take precedence over identically-named
	# CNAMEs requested by a -c option with "mode=D".
	# Prevent the generation of an illegal duplicate DNS record by
	# removing it from the deferred list.
	#
	($tmp, $netpat, $tmp) = split(' ', $cAliases{$canonical}, 3);
	$cmode = $cModeSpec{$netpat};
	print STDERR "Can't create CNAME for `$canonical.$cpatrel{$netpat}'; another RR exists.\n" if $verbose && $cmode !~ /Q/;
	delete($cAliases{$canonical});
    }
}

if (keys(%cAliases)) {
    #
    # The deferred set of non-conflicting CNAMEs can finally be created.
    #
    while (($alias, $data) = each %cAliases) {
	if (exists($RRowners{$alias})) {
	    $match = $RRowners{$alias};
	    1 while $match =~ s/ (SIG|NXT|KEY) / /;
	} else {
	    $match = " ";
	}
	($canonical, $netpat, $ttl) = split(' ', $data, 3);
	$cmode = $cModeSpec{$netpat};
	$cDomain = $cpatrel{$netpat};
	if ($cmode =~ /I/) {
	    $cDomain =~ s/$Domainpattern$//;
	} else {
	    $cDomain .= ".";
	}
	if ($match eq " ") {
	    &PRINTF(*DOMAIN, "%s%s\tCNAME\t%s.%s\n", &TAB($alias, 16), $ttl, $canonical, $cDomain);
	    if (exists($RRowners{$alias})) {
		$RRowners{$alias} .= "CNAME ";
	    } else {
		$RRowners{$alias} = " CNAME ";
	    }
	    $tmp = $alias;
	    while ($tmp =~ /(\\[.]|[^.])*\./) {
		$tmp =~ s/(\\[.]|[^.])*\.//;
		$RRowners{$tmp} = " " unless exists($RRowners{$tmp});
	    }
	} else {
	    print STDERR "Can't create CNAME for `$canonical.$cpatrel{$netpat}'; another RR exists.\n" if $verbose && $cmode !~ /Q/;
	}
    }
}

if (keys(%deferredPTR) || keys(%pendingPTR)) {
    #
    # Make sure that deferred PTR records are output.
    #
    if (keys(%HostsPTR)) {
	#
	# Look for domain names that matched a -p option which also
	# point to multi-homed hosts.  The methodology is basically
	# the same as in the CNAME subroutine.
	#
	while (($canonical, $data) = each %HostsPTR) {
	    ($pDomain, @addrs) = split(' ', $data);
	    if (@addrs > 1) {
		#
		# Found a multi-homed host.
		#
		foreach $addr (@addrs) {
		    @aliases = split(' ', $AliasesPTR{"$canonical-$addr"});
		    foreach $alias (@aliases) {
			#
			# For every IP address, check each alias for the
			# following:
			#
			#   1. Skip aliases that are identical to the FQDN.
			#   2. Skip aliases that are common to all addresses
			#      since they have CNAMEs assigned to them.
			#   3. Make the necessary fix-ups so that the PTR record
			#      will point to the first non-common alias since it
			#      is this alias that will have an Address RR in the
			#      forward-mapping data file.
			#   4. Do nothing if we run out of aliases.  The default
			#      PTR record which points to the canonical name
			#      will be generated in the subsequent block.
			#
			next if $canonical eq "$alias.$pDomain." || $canonical eq "$alias.";
			$common_alias = 1;
			foreach $tmp (@addrs) {
			    unless ($AliasesPTR{"$canonical-$tmp"} =~ /(^|\s)$alias\s/) {
				$common_alias = 0;
				last;
			    }
			    last unless $common_alias;
			}
			if ($common_alias) {
			    #
			    # Remove the alias from this as well as the other
			    # addresses of this host so that it won't be
			    # encountered again.
			    #
			    foreach $tmp (@addrs) {
				$AliasesPTR{"$canonical-$tmp"} =~ s/(^|\s)$alias\s/$1/;
			    }
			} elsif (exists($deferredPTR{"$canonical-$addr"})) {
			    #
			    # Make the necessary updates so that reverse-mapping
			    # queries are answered with the unique interface
			    # name instead of the multi-address canonical name.
			    #
			    ($ptr_file, $ptr_owner, $ttl) = split(' ', $deferredPTR{"$canonical-$addr"}, 3);
			    $tmp = $alias;
			    $tmp .= ".$pDomain." unless $alias =~ /\.$/;
			    $pendingPTR{$ptr_file}{$ptr_owner} = "$tmp $ttl";
			    delete($deferredPTR{"$canonical-$addr"});
			    last;	# finished with this IP address
			}
		    }
		}
	    }
	}
	scalar(keys(%deferredPTR));	# Reset the hash iterator before leaving
    }
    while (($canonical, $data) = each %deferredPTR) {
	#
	# Anything left over in the deferred PTR hash gets the default
	# treatment - a PTR record that points to the canonical name.
	#
	$canonical =~ s/-(\d+[.]){3}\d+$//;
	($ptr_file, $ptr_owner, $ttl) = split(' ', $data, 3);
	$pendingPTR{$ptr_file}{$ptr_owner} = "$canonical $ttl";
    }
    scalar(keys(%pendingPTR));
    while (($ptr_file, $addr) = each %pendingPTR) {
	foreach $ptr_owner (sort { $a cmp $b } keys %{ $pendingPTR{$ptr_file} }) {
	    ($canonical, $ttl) = split(' ', $pendingPTR{$ptr_file}{$ptr_owner}, 2);
	    unless ($ptr_file eq *DOMAIN) {
		$canonical .= ".$Domain." unless $canonical =~ /\.$/;
		&PRINTF($ptr_file, "%s%s\tPTR\t%s\n", &TAB($ptr_owner, 8), $ttl, $canonical);
	    } elsif (exists($RRowners{$ptr_owner})
		     && $RRowners{$ptr_owner} =~ / CNAME /) {
		print STDERR "Can't create PTR record for `$ptr_owner' due to an existing CNAME RR.\n" if $verbose;
	    } else {
		&PRINTF(*DOMAIN, "%s%s\tPTR\t%s\n", &TAB($ptr_owner, 16), $ttl, $canonical);
		if (exists($RRowners{$ptr_owner})) {
		    $RRowners{$ptr_owner} .= "PTR " unless $RRowners{$ptr_owner} =~ / PTR /;
		} else {
		    $RRowners{$ptr_owner} = " PTR ";
		}
		$data = $ptr_owner;
		while ($data =~ /(\\[.]|[^.])*\./) {
		    $data =~ s/(\\[.]|[^.])*\.//;
		    $RRowners{$data} = " " unless exists($RRowners{$data});
		}
	    }
	}
    }
}
	
# Deal with 'spcl' files.
#
if (-r $Specialfile) {
    &PRINTF(*DOMAIN, "\n\$INCLUDE $Pwd/%s\n", $Specialfile);
    print STDOUT "File `$Specialfile' included.\n" if $verbose;
}
scalar(keys(%Net_zones));
while (($key, $data) = each %Net_zones) {
    ($zone_name, $zone_file) = split(' ', $data, 2);
    if (-r "spcl.$zone_file") {
	$data = $Net_ranges{$key};
	($ptr_file, $ptr_template) = split(' ', $data, 2);
	&PRINTF($ptr_file, "\n\$INCLUDE $Pwd/spcl.%s\n", $zone_file);
	print STDOUT "File `spcl.$zone_file' found and included.\n" if $verbose;
	#
	# Make sure to disable checking of single-character hostnames
	# and aliases since this RFC-952 restriction is limited to
	# entries in the host table.
	#
	$tmp_rfc952 = $rfc952;
	$rfc952 = 0;

	# Since all forward-mapping data has now been accounted for,
	# the "READ_RRs" subroutine will be able to issue a warning
	# if it detects a PTR record which points to an in-zone CNAME.
	#
	$zone_name .= ".";
	$newline_printed = &READ_RRs("spcl.$zone_file", $zone_name, $zone_name,
				     $zone_name, 0);
	if ($verbose) {
	    print STDERR "\n" while $newline_printed--;
	} else {
	    $newline_printed = 0;
	}
	$rfc952 = $tmp_rfc952;
    }
}
if ($Open_DB_files) {
    #
    # Close any DB files that may still be open since we are finished
    # with the task of writing resource records to them.
    #
    scalar(keys(%LRUtable));
    while (($key, $data) = each %LRUtable) {
	&CLOSE($key) if $data;
    }
}

print STDOUT "Generating boot and conf files...\n" if $verbose;
&GEN_BOOT;

if ($audit && $verbose) {
    #
    # Take the opportunity to undefine some data structures that are
    # no longer needed.  The memory space can then be recycled by the
    # AUDIT_RRs subroutine which itself needs to create still more
    # data structures.
    #
    undef %Aliases;
    undef %Comments;
    undef %Ttl;
    undef %deferredPTR;
    undef %pendingPTR;
    undef %pPTR;
    undef %HostsPTR;
    undef %AliasesPTR;
    undef %cAliases;
    undef %cModeSpec;
    undef %pModeSpec;
    undef %cpatrel;
    undef %ptrpatrel;
    undef %CommentRRs if $Commentfile;
    undef %DB_filehandle;
    undef %LRUtable;
    undef %Net_ranges;
    undef %Net_zones;
    undef %MasterZoneOptions;
    undef %SlaveZoneOptions;
    undef @BootOptions;
    undef @ConfLogging;
    undef @ConfOptions;
    undef @GlobalMasterZoneOptions;
    undef @GlobalSlaveZoneOptions;
    undef @aliases;
    undef @bootmsgs;
    undef @cpats;
    undef @ptrpats;
    undef @elimpats;
    undef @makesoa;

    # As a paranoid exercise, make sure the "$data_fname" variable
    # undergoes the same sanitation as is performed in the VERIFY_ZONE
    # subroutine so that there are no nasty surprises with temporary
    # filenames that can't be created.
    #
    $data_fname = lc($Domain);		# override a -P option
    for ($data_fname) {
	s/\\([<|>&\(\)\$\?@;'`])/$1/g;	# unescape special characters
	s/[\/<|>&\[\(\)\$\?;'`]/%/g;	# change the bad ones to "%"
	s/\\\s/_/g;			# change tabs and spaces to underscores
	s/\\//g;			# remove any remaining escapes chars.
    }

    print STDOUT "Checking NS, MX, and other RRs for various improprieties...\n";
    &AUDIT_RRs(0);
}

if (($Load_Status == 2 && $DefAction eq "Skipping") || $Load_Status > 2) {
    ($message = <<EOT) =~ s/^\s+\|//gm;
    |Attention! One or more of the above errors is sufficiently severe to
    |           either prevent the zone from being loaded or the bad data
    |           may cause interoperability problems with other name servers.
EOT
    print STDERR "$message\n" if $verbose;
    $error = 1;
} else {
    $error = 0;
}
print STDOUT "Done.\n" if $verbose;
exit($error);


#
# Subroutine to check for bad names
# No check is made for maximum length of hostnames and/or domain labels.
#
# Return values:
#   0 = valid name within the selected checking context
#   1 (not returned - reserved)
#   2 = invalid name which violates RFC-1123 and/or RFC-952
#   3 = invalid DNS name within all checking contexts
#
sub CHECK_NAME {
    my ($name, $rrtype) = @_;
    my $bad_chars;

    # Regular Expression processing can get expensive in terms
    # of processing time.  Since this subroutine can be called
    # literally thousands of times during a processing run,
    # it is imperative that duplicate processing is eliminated.

    if ($rfc1123 && $rrtype =~ /^(A|MX|WKS)$/) {
	#
	# The name checking that is done in this block is a
	# superset of what is done for other other record types
	# or when RFC-1123 checking is not in effect.  Therefore,
	# we will be able to exit with a definitive result.
	#
	# At this point, RFC-1123 name checking is in effect for
	# canonical hostnames in the host table as well as DNS
	# domain names with the following record types: A, MX, or WKS.
	#
	# The character set of such names is limited to digits,
	# the minus sign (-), the period (.), and any mix of
	# upper/lowercase letters.  Hostnames and domain label
	# names must not begin or end with a minus sign or period.
	# The RFC-952 prohibition against names beginning with
	# numbers is lifted and hostnames can now have a maximum
	# length of 255 characters.
	#
	# The RDATA fields of certain other record types also
	# qualify for more stringent name checking according to
	# the following table:
	#
	#         RRtype    owner field    RDATA field
	#	  ------    -----------    -----------
	#           A          yes             n/a
	#           MX         yes             yes (exchange-dname portion)
	#          WKS         yes             n/a
	#           NS         no              yes
	#           RP         no	       yes (mbox-dname portion)
	#          SOA         no              yes (MNAME and RNAME)
	#          PTR         no              yes
	#          SRV         no              yes
	#          NSAP        no              n/a
	#         AFSDB        no              yes (hostname portion)
	#           RT         no              yes (intermediate-host portion)
	#           
	# These RDATA fields make it to this point in the subroutine
	# by calling CHECK_NAME with a record type of "A".
	#
	if ($name =~ /^[.-]|[.-]$|[.][.]|[.]-|-[.]/) {
	    #
	    # This hostname/domain name is bad but we now need
	    # to know how bad.  If it merely violates RFC-1123
	    # and we're processing a host table, it's possible
	    # to let the user off with a warning depending on
	    # what `-I' option is in effect.
	    # A violation of the basic DNS naming rules, however,
	    # means the name has no hope of being loaded by a
	    # BIND name server.  In this case, we must set the
	    # return code to a special value so that the hostname
	    # can be skipped if we're processing a host table.
	    #
	    $name =~ s/-//g;
	    return 3 if $name =~ /^[.]|[.][.]|[.]$/;
	    return 2;
	}
	$bad_chars = $name;
	$bad_chars =~ tr/A-Za-z0-9.-//d;
	return 2 if $bad_chars;
	return 2 if $rfc952 && $rrtype eq "A" && length($name) == 1;
	return 0;
    }

    # Regardless of the state of the "$rfc1123" flag, this section
    # of the subroutine is limited to checking for the basic validity
    # of a DNS domain name.

    # Domain names must not begin or end with an unescaped
    # dot nor contain adjacent unescaped dots.
    #
    if ($name =~ /^[.]|[^\\][.]$|[^\\][.][.]/) {
	#
	# No matter what level of name checking is in effect,
	# we've encountered a basic DNS name error that will
	# prevent a zone from being loaded by a BIND name server.
	# If the host table is being processed, unilaterally
	# declare that the entry is being skipped by setting
	# a special return value.
	#
	return 3;
    }
    return 2 if $rfc952 && $rrtype eq "CNAME" && length($name) == 1;
    return 0;
}


#
# Subroutine to fit a name into a tab-delimited space
#
sub TAB {
    my ($name, $field) = @_;
    my $tabs;

    $tabs = $field - length($name);
    unless ($tabs > 0) {
	return "$name ";
    }
    while ($tabs > 0) {
	$name .= "\t";
	$tabs -= 8;
    }
    return $name;
}


#
# Subroutine to print to a DB file and utilize a data structure
# to accommodate limits on the number of concurrently-open files.
# NOTE: Do not use this subroutine to write to non-DB (zone data)
#       files unless their filenames and filehandles have been
#       registered in the %DB_filehandle and %LRUtable hashes.
# 
# As a host file is processed, the appropriate reverse-mapping
# DB files are opened as the IP addresses are encountered.
# Since there's no requirement that a host file be sorted in
# numerical IP address order, there's nothing in the input data
# stream that can signal us when a file can be closed.  So we
# just keep opening files.
#
# Depending on the size of the host file, we may eventually
# reach the limit of concurrently-open files imposed by either
# the computer's operating system or the -L option of `h2n'.
# A file must be closed in order to free up the resources for
# the new file to be opened.  Ideally, we should choose the
# Least Recently Used (LRU) file for closure.
#
# This subroutine will maintain the %LRUtable hash to identify
# the LRU files.  Each time a record is written to a DB file,
# the "$Print_sequence" number is incremented and assigned as
# the value of the corresponding filehandle key in %LRUtable.
# When the OPEN() subroutine needs to close a file, it will
# sort %LRUtable and close the file with the smallest, i.e.,
# oldest, print sequence number.
#
sub PRINTF {
    my (@args, $output_file);
    local *FILE;
    (*FILE, @args) = @_;

    unless ($LRUtable{*FILE}) {
	#
	# The DB file is closed - re-open it in append mode.
	#
	$output_file = "$DB_filehandle{*FILE}";
	unless (&OPEN(*FILE, ">> $output_file")) {
	    print STDERR "Couldn't re-open `$output_file' in \&PRINTF().\n";
	    print STDERR "Error: $!\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
    }
    if (printf FILE @args) {
	$LRUtable{*FILE} = ++$Print_sequence;
    } else {
	$output_file = "$DB_filehandle{*FILE}";
	print STDERR "Couldn't write to `$output_file' in \&PRINTF().\n";
	print STDERR "Error: $!\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    }
}


#
# Subroutine to open a file.  If necessary, a currently-open
# file will be closed if all available filehandles are in use.
# NOTE: Unlike the PRINTF() subroutine above, the OPEN() and
#       CLOSE() subroutines may be called to service non-DB
#       files.  Currently-open DB files will be closed as
#       necessary to open a non-DB file.
#       An example of where this is used is in READ_RRs().
#       The LRU service of OPEN() is not really needed to
#       process a forward-mapping `spcl' file since this is
#       done before the host file is read.  However, READ_RRs()
#       is also called after the host file is read if any
#       reverse-mapping `spcl' files are discovered.
#       Here, the LRU service of OPEN() is needed because of
#       the large number of DB files that may be open as a
#       result of processing the host file.
#
sub OPEN {
    my ($FH, $arg, $ok, $tries, @sortedFH);
    local *FILE_HANDLE;
    (*FILE_HANDLE, $arg) = @_;

    if ($Open_DB_files < $Open_file_limit) {
	$ok = open(*FILE_HANDLE, $arg);
    }
    unless ($ok) {
	#
	# Assume that we have exceeded "$Open_file_limit (-L option)
	# or we've run into a limit set by the operating system.
	# Either way, we'll sort %LRUtable to find the least recently
	# used file that's still open and close it to make room for
	# the new file that needs to be opened.
	#
	@sortedFH = sort { $LRUtable{$a} <=> $LRUtable{$b}; } keys %LRUtable;
	$tries = 0;
	foreach $FH (@sortedFH) {
	    if ($LRUtable{$FH} > 0) {
		&CLOSE($FH);		# CLOSE() decrements "$Open_DB_files"
		$tries++;		# and sets $LRUtable{$FH} to zero
		$ok = open(*FILE_HANDLE, $arg);
		#
		# It's reasonable to assume that a single CLOSE() will be
		# sufficient for the new OPEN() to succeed.  Be flexible,
		# however, and allow three tries.  Bail out if the OPEN()
		# still fails since the operating system may be complaining
		# about a problem that's unrelated to concurrently-open files.
		#
		last if $ok || $tries >= 3;
	    }
	}
    }
    if ($ok) {
	$Open_DB_files++ if exists($DB_filehandle{*FILE_HANDLE});
	return $ok;
    } else {
	return;		# return the undefined value just like the real open()
    }
}


#
# Subroutine to close a file and maintain the %DB_filehandle
# data structure for managing DB files.
#
sub CLOSE {
    my ($ok);
    local *FHANDLE;
    (*FHANDLE) = @_;

    $ok = close(*FHANDLE);
    if ($ok && exists($DB_filehandle{*FHANDLE})) {
	$Open_DB_files-- if $Open_DB_files > 0;
	$LRUtable{*FHANDLE} = 0;
    }
}


#
# Generate resource record data for strings from the comment
# field that are found in the comment file (-C).
#
sub DO_COMMENTS {
    my ($canonical, @addrs) = @_;
    my (@c, $c, $addr, $comments, $class, $rrtype, $tmp);

    foreach $addr (@addrs) {
	$comments .= " " . $Comments{"$canonical-$addr"};
    }

    @c = split(' ', $comments);
    foreach $c (@c) {
	if (exists($CommentRRs{$c})) {
	    ($class, $rrtype, $tmp) = split(' ', $CommentRRs{$c}, 3);
	    unless ($class =~ /^(IN|CH|HS)$/) {
		#
		# Assume the optional CLASS field was omitted (defaults
		# to IN) and that "$class" has the parsed RR type.
		#
		$rrtype = $class
	    }
	    $rrtype = uc($rrtype);
	    if (exists($RRowners{$canonical}) && $RRowners{$canonical} =~ / CNAME /) {
		print STDERR "Can't create $rrtype record for `$canonical' due to an existing CNAME RR.\n" if $verbose;
	    } else {
		&PRINTF(*DOMAIN, "%s%s\t%s\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $CommentRRs{$c});
		$nameset = $canonical;
		if (exists($RRowners{$canonical})) {
		    $RRowners{$canonical} .= "$rrtype " unless $RRowners{$canonical} =~ / $rrtype /;
		} else {
		    $RRowners{$canonical} = " $rrtype ";
		}
	    }
	}
    }
}


#
# Generate MX record data
#
sub MX {
    my ($canonical, @addrs) = @_;
    my ($LocalHost, $addr, $comments, $global, $rafcp, $rdata, $self);

    $LocalHost = 0;
    foreach $addr (@addrs) {
	$comments .= " " . $Comments{"$canonical-$addr"};
	$LocalHost = 1 if $addr eq $localhost;
    }

    # As of version 2.45, the "[smtp]" flag by itself is sufficient
    # to suppress the global MX records (-m option) and leave only
    # the self-pointing MX record.  The "[smtp] [no mx]" combination
    # no longer has to be specified.  Existing host file entries that
    # have "[smtp] [no mx]" will continue to work as before.
    #
    $self = $global = 1; $rafcp = 0;
    $self = $global = 0 if $comments =~ /\[\s*no(\s*-*\s*)?mx\s*\]/i;
    $self = 0 if $comments =~ /\[\s*no(\s*-*\s*)?smtp\s*\]/i;
    if ($comments =~ /\[\s*smtp((\s*-*\s*)?only)?\s*\]/i) {
	$self = 1;
	$global = 0;
    }
    if ($comments =~ /\[\s*rafcp\s*\]/i) {
	$rafcp = 1;
	$self = $global = 0;
    }

    if (exists($RRowners{$canonical}) && $RRowners{$canonical} =~ / CNAME /) {
	print STDERR "Can't create WKS record for `$canonical' due to an existing CNAME RR.\n" if ($rafcp || $do_wks) && $verbose;
	print STDERR "Can't create MX record for `$canonical' due to an existing CNAME RR.\n" if ($self || $global) && $verbose;
	return;
    }
    # If `[rafcp]' is specified in the comment section, add in a WKS record,
    # and do not add any MX records.
    #
    if ($rafcp) {
	foreach $addr (@addrs) {
	    &PRINTF(*DOMAIN, "%s%s\tWKS\t%s rafcp\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $addr);
	    $nameset = $canonical;
	}
	if (exists($RRowners{$canonical})) {
	    $RRowners{$canonical} .= "WKS " unless $RRowners{$canonical} =~ / WKS /;
	} else {
	    $RRowners{$canonical} = " WKS ";
	}
    } elsif (!$LocalHost) {
	if ($self) {
	    # Add WKS if requested
	    if ($do_wks) {
		foreach $addr (@addrs) {
		    &PRINTF(*DOMAIN, "%s%s\tWKS\t%s tcp smtp\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $addr);
		    $nameset = $canonical;
		}
		if (exists($RRowners{$canonical})) {
		    $RRowners{$canonical} .= "WKS " unless $RRowners{$canonical} =~ / WKS /;
		} else {
		    $RRowners{$canonical} = " WKS ";
		}
	    }
	    &PRINTF(*DOMAIN, "%s%s\tMX\t%s %s\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $DefMxWeight, $canonical);
	    $nameset = $canonical;
	    if (exists($RRowners{$canonical})) {
		$RRowners{$canonical} .= "MX " unless $RRowners{$canonical} =~ / MX /;
	    } else {
		$RRowners{$canonical} = " MX ";
	    }
	}
	if (@MX > 0 && $global) {
	    foreach $rdata (@MX) {
		&PRINTF(*DOMAIN, "%s%s\tMX\t%s\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $rdata);
		$nameset = $canonical;
	    }
	    if (exists($RRowners{$canonical})) {
		$RRowners{$canonical} .= "MX " unless $RRowners{$canonical} =~ / MX /;
	    } else {
		$RRowners{$canonical} = " MX ";
	    }
	}
    }
}


#
# Generate RP record data
#
sub RP {
    my ($canonical, @addrs) = @_;
    my ($addr, $comments, $rp, $rptxt, $user_part, $domain_part);

    if (exists($RRowners{$canonical}) && $RRowners{$canonical} =~ / CNAME /) {
	print STDERR "Can't create RP record for `$canonical' due to an existing CNAME RR.\n" if $verbose;
	return;
    }

    foreach $addr (@addrs) {
	$comments .= " " . $Comments{"$canonical-$addr"};
    }

    # Be liberal in what we accept, e.g.,
    #
    #   [rp=first.last@host"text"]  [ rp = first.last@host "text" ]
    #   [rp = first.last@host random "text" string ]
    #
    #   all result in RP  MAILBOX  = first\.last.host
    #                 RP  TXTDNAME = current canonical domain name
    #                 TXT RDATA    = "text"
    #
    #   [rp=first.last@host]  [rp= first.last@host "" ]
    #   [rp = first.last@host random "" string ]
    #   [rp = first.last@host random  string ]
    #
    #   all result in RP  MAILBOX  = first\.last.host
    #                 RP  TXTDNAME = . (root zone placeholder)
    #                 no TXT record
    #
    #   [rp="text"]  [ rp = "text" ]  [rp = "text" random string ]
    #
    #   all result in RP  MAILBOX  = . (root zone placeholder)
    #                 RP  TXTDNAME = current canonical domain name
    #                 TXT RDATA    = "text"
    #
    if ($comments =~ /\[\s*rp\s*=\s*([^\s"]+)?[^"]*("[^"]*")?[^\]]*\]/i) {
	$rp = ($1) ? $1 : ".";
	$rptxt = ($2) ? $2 : "";
	$rptxt =~ s/"//g;
	if ($rp =~ /@/) {
	    ($user_part, $domain_part) = split(/@/, $rp, 2);
	    $user_part =~ s/\./\\./g;		# escape "." in username
	    1 while $user_part =~ s/\\\\/\\/g;	# remove redundancies
	    if ($domain_part =~ /\./) {		# multiple domain labels
		$domain_part .= ".";		# append root domain
	    }					# relative domain fmt. otherwise
	    $rp = "$user_part.$domain_part";	# rejoin w/ unescaped "."
	} elsif ($rp !~ /\.$/) {		# proceed if no trailing "."
	    $rp =~ s/\./\\./g;			# treat as username & escape "."
	    1 while $rp =~ s/\\\\/\\/g;		# remove redundancies
	}					# leave username in relative fmt
	$rp =~ s/\.\././g;			# remove redundant "." chars.
	&PRINTF(*DOMAIN, "%s%s\tRP\t%s %s\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $rp, ($rptxt eq "" ? "." : "$canonical"));
	if (exists($RRowners{$canonical})) {
	    $RRowners{$canonical} .= "RP " unless $RRowners{$canonical} =~ / RP /;
	} else {
	    $RRowners{$canonical} = " RP ";
	}
	unless ($rptxt eq "") {
	    &PRINTF(*DOMAIN, "%s%s\tTXT\t\"%s\"\n", "\t\t", $ttl, $rptxt);
	    $RRowners{$canonical} .= "TXT " unless $RRowners{$canonical} =~ / TXT /;
	}
	$nameset = $canonical;
    }
}
    

#
# Generate TXT record data
#
sub TXT {
    my ($canonical, @addrs) = @_;
    my ($addr, $comments);

    foreach $addr (@addrs) {
	$comments .= " " . $Comments{"$canonical-$addr"};
    }

    for ($comments) {
	s/\[\s*no(\s*-*\s*)?smtp\s*\]//gi;
	s/\[\s*smtp((\s*-*\s*)?only)?\s*\]//gi;
	s/\[\s*no(\s*-*\s*)?mx\s*\]//gi;
	s/\[\s*rafcp\s*\]//gi;
	s/\[\s*ttl\s*=\s*(\d+|(\d+[wdhms])+)\s*\]//gi;
	s/\[\s*rp\s*=\s*([^\s"]+)?[^"]*("[^"]*")?[^\]]*\]//gi;
	s/\[\s*mh\s*=\s*(d|c|p|cp|pc)\s*\]//gi;
	s/\[\s*no(\s*-*\s*)?ptr\s*\]//gi;
	s/\[\s*(no|ignore)\s*-c\s*\]//gi;
	s/^\s+//;
	s/\s+$//;
    }
    
    if ($comments) {
	if (exists($RRowners{$canonical}) && $RRowners{$canonical} =~ / CNAME /) {
	    print STDERR "Can't create TXT record for `$canonical' due to an existing CNAME RR.\n" if $verbose;
	} else {
	    &PRINTF(*DOMAIN, "%s%s\tTXT\t\"%s\"\n", ($canonical eq $nameset ? "\t\t" : &TAB($canonical, 16)), $ttl, $comments);
	    $nameset = $canonical;
	    if (exists($RRowners{$canonical})) {
		$RRowners{$canonical} .= "TXT " unless $RRowners{$canonical} =~ / TXT /;
	    } else {
		$RRowners{$canonical} = " TXT ";
	    }
	}
    }
}


#
# Generate resource records (CNAME or A) for the aliases of a
# canonical name.  This subroutine is called after the generation
# of all of the canonical name's other RRs (A, MX, TXT, etc.).
#
sub CNAME {
    my ($canonical, @addrs) = @_;
    my ($addr, $alias, $common_alias, $default_method, $interface_alias);
    my ($make_rr, $numaddrs, $ptr_file, $rr_written, $tmp, @aliases);

    $rr_written = 0;
    $numaddrs = @addrs;
    foreach $addr (@addrs) {
	#
	# If this is a single-address host, print a CNAME record
	# for each alias.
	# 
	# If this is a multi-homed host, perform the following tasks
	# for each alias of each IP address:
	#
	#   1. Identify aliases that are common to all addresses.
	#      If possible, a CNAME pointing to the canonical name
	#      will be created.
	#
	#   2. The first non-common alias will be assigned an A record
	#      and, if enabled, the appropriate MX RRset.
	#
	#   3. If the default method of handling multi-homed hosts is
	#      in effect, then do the following:
	#
	#      * Subsequent non-common aliases are assigned the same
	#        RRset(s) as the first alias in step #2.
	#
	#      Otherwise, generate the rest of the forward-mapping RRs
	#      for the multi-homed host using the following alternative:
	#
	#      * Subsequent non-common aliases will be assigned a CNAME
	#        that points to the A record created in step #2.
	#
	if ($numaddrs > 1) {
	    #
	    # Each address of a multi-homed host may specify how the
	    # forward- and reverse-mapping RRsets get generated via
	    # the "[mh=??]" flag together with the +m option.
	    # Determine the forward-mapping method that's now in effect.
	    #
	    unless ($multi_homed_mode =~ /C/) {
		#
		# Use the default method unless overridden.
		#
		$default_method = ($Comments{"$canonical-$addr"} =~ /\[\s*mh\s*=\s*(c|cp|pc)\s*\]/i) ? 0 : 1;
	    } else {
		#
		# Use the alternate method unless overridden.
		# NOTE: The absence of the "c" specification in the
		#       comment flag signifies an override condition.
		#
		$default_method = ($Comments{"$canonical-$addr"} =~ /\[\s*mh\s*=\s*[dp]\s*\]/i) ? 1 : 0;
	    }
	}
	@aliases = split(' ', $Aliases{"$canonical-$addr"});
	$interface_alias = "";
	foreach $alias (@aliases) {
	    #
	    # Skip over the alias if it and the canonical name differ
	    # only in that one of them has the domain appended to it.
	    #
	    $alias =~ s/$Domainpattern$//i;

	    # If "$UseDefaultDomain" is in effect (-d domain mode=D),
	    # the following typo is not caught when the host file is
	    # read by the main section of the program:
	    #
	    #   host.domain   (correct)
	    #   host .domain  (typo)
	    #
	    # The ".domain" fragment gets interpreted as an alias and will
	    # be rendered to the null string by the previous statement.
	    # Make sure that null aliases are also skipped over.
	    # Otherwise, havoc will ensue later in this subroutine.
	    #
	    next if !$alias || $alias eq $canonical;

	    $common_alias = 0;
	    if ($numaddrs > 1) {
		#
		# If the alias exists for *all* addresses of this host,
		# we can use a CNAME instead of an Address record.
		#
		$common_alias = 1;
		foreach $tmp (@addrs) {
		    unless ($Aliases{"$canonical-$tmp"} =~ /(^|\s)$alias\s/) {
			$common_alias = 0;
			last;
		    }
		}
	    }

	    if ($numaddrs > 1 && !$common_alias && !$interface_alias) {
		unless ($default_method) {
		    #
		    # Only the current alias will be assigned an A record.
		    # Subsequent non-common aliases will be assigned CNAME
		    # records that point back to this one.
		    # Initialize the $interface_alias variable with the
		    # domain name to which the CNAME(s) will reference.
		    # The variable assignment will also prevent subsequent
		    # aliases of the current address from be processed by
		    # this A-record block.
		    #
		    $interface_alias = $alias;
		}
		if (exists($RRowners{$alias}) && $RRowners{$alias} =~ / CNAME /) {
		    print STDERR "Can't create A record for `$alias' due to an existing CNAME RR.\n" if $verbose;
		    $make_rr = 0;
		} else {
		    $make_rr = 1;
		    $error = &CHECK_NAME($alias, 'A');
		    if ($error) {
			$action = ($error == 3) ? "Skipping" : $DefAction;
			$make_rr = 0 unless $action eq "Warning";
			if ($verbose) {
			    if ($make_rr) {
				print STDERR "Warning: non-RFC-compliant Address record (`$alias') being generated.\n";
			    } else {
				print STDERR "Cannot generate Address record for `$alias' (invalid hostname).\n";
			    }
			    print STDERR "It is an alias for `$canonical', but CNAME not possible (multi-homed).\n";
			}
		    }
		}
		if ($make_rr) {
		    &PRINTF(*DOMAIN, "%s%s\tA\t%s\n", ($alias eq $nameset ? "\t\t" : &TAB($alias, 16)), $ttl, $addr);
		    $nameset = $alias;
		    $rr_written = 1;
		    #
		    # Keep track of domain names that now have Address RRs
		    # assigned to them (we can't make these registrations
		    # in the %Hosts hash because we are in a loop that is
		    # serially reading that data structure with the each()
		    # function).
		    # This data will be used to prevent the creation
		    # of conflicting CNAMEs and, if auditing is enabled,
		    # to make sure that in-domain NS and MX RRs point to
		    # domain names that have at least one Address record.
		    #
		    if (exists($RRowners{$alias})) {
			$RRowners{$alias} .= "A " unless $RRowners{$alias} =~ / A /;
		    } else {
			$RRowners{$alias} = " A ";
		    }
		    $data = $alias;
		    while ($data =~ /(\\[.]|[^.])*\./) {
			#
			# The unqualified alias consists of two or more labels.
			# Register the interior labels in the %RRowners hash
			# so that we can correctly distinguish between a
			# non-existent domain name and a domain name with no
			# DNS resource records during the auditing phase.
			#
			$data =~ s/(\\[.]|[^.])*\.//;	# strip leading label
			$RRowners{$data} = " " unless exists($RRowners{$data});
		    }

		    if ($do_mx) {
			#
			# Ensure that every Address RR has the accompanying
			# MX RRset.  First, however, the comment flags that
			# are tied to the canonical name of this particular
			# address must be copied to a key based on the current
			# alias.
			#
			$Comments{"$alias-$addr"} = $Comments{"$canonical-$addr"};
			&MX($alias, ($addr));
		    }

		    if (exists($deferredPTR{"$canonical-$addr"})) {
			#
			# Update the deferred PTR hash so that reverse-mapping
			# queries are answered with the unique interface name
			# instead of the multi-address canonical name.
			#
			($ptr_file, $ptr_owner, $tmp) = split(' ', $deferredPTR{"$canonical-$addr"});
			$pendingPTR{$ptr_file}{$ptr_owner} = "$alias $tmp";
			delete($deferredPTR{"$canonical-$addr"});
		    }
		}
	    } else {
		#
		# Make sure that the alias does not already have an
		# assigned domain name before creating a CNAME record.
		#
		if (exists($RRowners{$alias})) {
		    #
		    # Accommodate any DNSSEC-related RRs from a `spcl'
		    # file that are allowed to co-exist with CNAMEs
		    # per RFC-2065.
		    #
		    $tmp = $RRowners{$alias};
		    1 while $tmp =~ s/ (SIG|NXT|KEY) / /;
		} else {
		    $tmp = " ";
		}
		if ($tmp ne " " || exists($Hosts{$alias})) {
		    print STDERR "Resource record already exists for `$alias'; alias ignored.\n" if $verbose;
		    $make_rr = 0;
		} else {
		    $make_rr = 1;
		    $error = &CHECK_NAME($alias, 'CNAME');
		    if ($error) {
			$action = ($error == 3) ? "Skipping" : $DefAction;
			$make_rr = 0 unless $action eq "Warning";
			if ($verbose) {
			    if ($make_rr) {
				print STDERR "Warning: creating non-RFC-compliant CNAME record for alias `$alias'.\n";
			    } else {
				print STDERR "Cannot create CNAME record for `$alias' (invalid alias).\n";
			    }
			}
		    }
		    if ($make_rr) {
			if ($numaddrs == 1 || $common_alias) {
			    $tmp = $canonical;
			} else {
			    $tmp = $interface_alias;
			}
			&PRINTF(*DOMAIN, "%s%s\tCNAME\t%s\n", &TAB($alias, 16), $ttl, $tmp);
			$nameset = $alias;
			$rr_written = 1;
			if (exists($RRowners{$alias})) {
			    $RRowners{$alias} .= "CNAME ";
			} else {
			    $RRowners{$alias} = " CNAME ";
			}
			$data = $alias;
			while ($data =~ /(\\[.]|[^.])*\./) {
			    $data =~ s/(\\[.]|[^.])*\.//;
			    unless (exists($RRowners{$data})) {
				$RRowners{$data} = " ";
			    }
			}
			if ($audit && $verbose && $alias =~ /^\*($|\.)/) {
			    #
			    # Register the wildcard CNAME in the hash
			    # that is reserved for this purpose.
			    #
			    $tmp = $alias;
			    $tmp .= ".$Domain." unless $tmp =~ /$Domainpattern\.$/i;
			    $tmp =~ s/^\*\.//;
			    $Wildcards{$tmp} = " CNAME ";
			}
		    }
		}
	    }

	    if ($common_alias) {
		#
		# Since a CNAME record was either created or otherwise
		# accounted for, remove this name from the alias list so
		# it's not encountered again for the next address of this host.
		#
		foreach $tmp (@addrs) {
		    $Aliases{"$canonical-$tmp"} =~ s/(^|\s)$alias\s/$1/;
		}
	    }
	    if ($rr_written && exists($cAliases{$alias})) {
		#
		# RRs for the default domain take precedence over identically-
		# named CNAMEs requested by a -c option with "mode=D".
		# Prevent the generation of an illegal duplicate DNS record
		# by removing the pending domain name from the deferred list.
		#
		($tmp, $netpat) = split(' ', $cAliases{$alias});
		$cmode = $cModeSpec{$netpat};
		print STDERR "Can't create CNAME for `$alias.$cpatrel{$netpat}'; another RR exists.\n" if $verbose && $cmode !~ /Q/;
		delete($cAliases{$alias});
	    }
	    $rr_written = 0;
	}
    }
}


#
# Convert a time period in symbolic notation to the equivalent
# number of seconds.  Repeated time periods are added together
# consistent with the behavior of BIND, e.g.,
#   "1w2d3h2h1d1w"  is calculated identically to "2w3d5h"
#
sub SECONDS {
    my ($input_time) = @_;
    my @weeks   = ("dhms", "w", 604800);
    my @days    = ("whms", "d", 86400);
    my @hours   = ("wdms", "h", 3600);
    my @minutes = ("wdhs", "m", 60);
    my @seconds = ("wdhm", "s", 1);
    my @time_interval = (@weeks, @days, @hours, @minutes, @seconds);
    my ($other_units, $this_unit, $multiplier, $time, $total_seconds);

    return $input_time if $input_time =~ /^\d*$/;
    $total_seconds = 0;
    while (($other_units, $this_unit, $multiplier) = @time_interval) {
	$time = $input_time;
	$time =~ s/(\d+[$other_units])//gi;
	$time =~ s/$this_unit/ /gi;
	$time =~ s/ (\d+)/+$1/g;
	$total_seconds += ($multiplier * eval($time)) if $time;
	splice(@time_interval, 0, 3);
    }
    return $total_seconds;
}


#
# Convert a time period in seconds to its equivalent symbolic format.
#
sub SYMBOLIC_TIME {
    my ($input_time) = @_;
    my @weeks   = ("w", 604800);
    my @days    = ("d", 86400);
    my @hours   = ("h", 3600);
    my @minutes = ("m", 60);
    my @time_interval = (@weeks, @days, @hours, @minutes);
    my ($num_seconds, $remainder, $time_symbol, $time_string, $units);

    $input_time = uc($input_time) if $input_time =~ /^\d+[wdhms]$/;
    return $input_time unless $input_time =~ /^\d+$/;
    return "${input_time}S" if $input_time < 60;
    $time_string = "";
    $remainder = $input_time;
    while (($time_symbol, $num_seconds) = @time_interval) {
	$units = int($remainder / $num_seconds);
	if ($units) {
	    $time_string .= "${units}${time_symbol}";
	    $remainder = $remainder % $num_seconds;
	}
	splice(@time_interval, 0, 2);
    }
    $time_string .= "${remainder}s" if $remainder;
    $time_string = uc($time_string) if $time_string =~ /^\d+[wdhms]$/;
    return $time_string;
}
	

#
# Subroutine to increment the SOA serial number according to the
# specifications in RFC-1982.  Called only when a fixed number
# (-i option) or a calendar-based format of YYYYMMDDvv or YYYYMMvvvv
# (-y [mode=D|M] option) is requested for an existing zone data file.
#
# The SOA serial number is an unsigned 32-bit integer that uses
# special arithmetic described in RFC-1982.  Basically, a serial
# number always increases through the range of 0-4294967295 with
# a maximum single increment value of 2147483647 (the maximum
# 31-bit unsigned value).  When an increment causes the value of
# 4294967295 to be exceeded, a wrap-around occurs and the remainder
# is added to zero.  Although the ending number is smaller than the
# starting number in absolute numerical terms, slave name servers will
# treat the wrap-around as a logical increment in the serial number's
# value (as long as the 2147483647 limit is observed) and request a
# transfer of the zone's changed data from the master name server.
# NOTE: Care must be taken to avoid successive increments in the serial
#       number on the master name server that, when taken together,
#       exceed 2147483647 without first making sure that all configured
#       slave name servers (delegated and stealth) are synchronized after
#       each individual increment.  Otherwise, any wrap-around effect will
#       not be noticed and the slaves will treat a smaller serial number
#       on the master as being "older" than their zone copy with a larger
#       numeric value and not transfer the updated zone.
# 
# Return list:
#   (serial, flag)
#   serial : incremented SOA serial number
#   flag   : 0 = requested serial number was within the RFC-1982 limit
#            1 = requested serial number was equal to the existing one
#            2 = requested serial number exceeded the RFC-1982 limit
#
sub INCREMENT_SERIAL {
    my ($current_serial) = @_;
    my ($current_month_format, $new_serial, $limit_flag, $tmp_serial);

    if ($NewSerial == $current_serial) {
	#
	# Never return an unchanged serial number.
	#
	if ($NewSerial == 4294967295) {
	    #
	    # Wrap around to the next serial number.  Because of
	    # ambiguities in the way that different name server
	    # implementations treat an SOA serial number of zero,
	    # set the incremented number to one instead.
	    #
	    $new_serial = 1;
	} else {
	    $new_serial = $NewSerial + 1;
	}
	if ($UseDateInSerial) {
	    #
	    # This is effectively the second update of the current
	    # zone data file in the same calendar day.
	    #
	    $limit_flag = 0;
	} else {
	    #
	    # Set the flag to issue a warning that the requested
	    # serial number had to be overridden.
	    # 
	    $limit_flag = 1;
	}
	return ($new_serial, $limit_flag);
    }
	
    if ($NewSerial > $current_serial) {
	if (($NewSerial - $current_serial) <= 2147483647) {
	    #
	    # No special serial number handling is necessary.
	    #
	    return ($NewSerial, 0);
	} else {
	    #
	    # Add the maximum RFC-1982 increment and
	    # return the appropriate warning flag.
	    #
	    $tmp_serial = $current_serial + 2147483647;
	    return ($tmp_serial, 2);
	}
    }

    # Deal with a value of "$NewSerial" that is numerically
    # less than the zone's existing serial number.

    if ($UseDateInSerial) {
	#
	# Take into account the possibility that the current
	# SOA serial number scheme is already calendar based.
	#
	if ($UseDateInSerial > 1 && ($current_serial - $NewSerial) <= 98) {
	    #
	    # This is the expected result for a site that uses the
	    # date-based format of YYYYMMDDvv since it unambiguously
	    # supports 100 changes per calendar day (versions 00 to 99).
	    #
	    $new_serial = $current_serial +1;
	    return ($new_serial, 0);
	}
	#
	# Accommodate busier sites that wish to use a calendar
	# format of YYYYMMvvvv as well as the occasional day
	# when there are more than 100 updates for users of
	# the YYYYMMDDvv format.  This allows for 10,000
	# changes per calendar month.
	#
	if ($UseDateInSerial == 1) {
	    $current_month_format = $NewSerial;
	} else {
	    #
	    # If the date format is YYYYMMDDvv, the "$UseDateInSerial"
	    # variable has been assigned the day-portion of the base
	    # serial number.  Subtract it to get the base YYYYMMvvvv
	    # format.
	    #
	    $current_month_format = $NewSerial - $UseDateInSerial;
	}
	if (($current_serial - $current_month_format) <= 9998) {
	    $new_serial = $current_serial +1;
	    return ($new_serial, 0);
	}
	# Not returning to the caller from this block implies the
	# remaining scenario in which the hostmaster is changing
	# to the calendar-based format from an ordinary serial
	# number which is greater by 10000 or more.
    }

    # Since the requested serial number is numerically less than
    # the current serial number, we must wrap around the maximum
    # serial number value of 4294967295 either now or the next
    # time that `h2n' is run in order to properly set the new
    # serial number.

    if ($current_serial <= 2147483648) {
	#
	# The wrap-around can not occur now because of the
	# maximum increment limitation.  Go as far as we can
	# and set the limit flag.
	#
	$tmp_serial = $current_serial + 2147483647;
	return ($tmp_serial, 2);
    }
    if ($NewSerial == 0) {
	#
	# Even though this SOA serial number is not recommended,
	# the user has chosen this value anyway.  Make it so,
	# since we are within the maximum increment range.
	# By explicitly accounting for this possibility here, we
	# can purposely avoid setting an interim serial number of
	# zero in the next block.
	#
	return (0, 0);
    }
    $tmp_serial = 2147483646 - (4294967295 - $current_serial);
    if ($tmp_serial < $NewSerial) {
	#
	# The wrap-around falls short of reaching the requested serial
	# number.  Add the maximum increment to the current serial and
	# set the limit flag.  If the computed interim serial number is
	# zero, subtract one to avoid possible interoperability issues.
	# 
	$tmp_serial = 4294967295 if $tmp_serial == 0;
	return ($tmp_serial, 2);
    } else {
	#
	# The requested serial number can be reached from the current
	# serial number within the maximum increment limit by wrapping
	# around the maximum serial number.
	#
	return ($NewSerial, 0);
    }
}


#
# Subroutine to create the zone apex records (SOA and NS) at the beginning
# of the db file.  If the -T option was specified, additional zone apex
# records for the forward-mapping domain will also be added.
# A $TTL directive will appear at the beginning of the db file unless
# our RFC-2308 status specifically prohibits it.
#
# Return values:
#   0 = desired SOA serial number denied due to RFC-1982 limits
#   1 = no SOA serial number warnings
#
sub MAKE_SOA {
    my ($SOA_expire, $SOA_minimum, $SOA_mname, $SOA_refresh, $SOA_retry);
    my ($SOA_rname, $TTL_directive, $current_serial, $data, $error);
    my ($fname, $found_TTL_directive, $limit_flag, $message, $new_serial);
    my ($rdata, $rrtype, $s, $serial, $tmp, $ttl, $uq_fname, @SOA_fields);
    local *FILEH;
    ($fname, $uq_fname, *FILEH) = @_;

    if (-e "$fname.log" || -e "$fname.jnl") {
	#
	# Not good.  The presence of a BIND 8 log file or BIND 9
	# journal file indicates that this is a dynamic zone.
	# Dynamic zones *must* be dynamically updated.
	# The risk of data loss is high if `h2n' is allowed
	# to overwrite a dynamic zone with a relatively static
	# snapshot of the host file data.
	#
	print STDERR "Dynamic zone log/journal found for `$fname'.\n";
	print STDERR "I refuse to risk loss of data ... sorry.\n";
	exit(2);
    }
    if (-s $fname) {
	$found_TTL_directive = 0;
	unless (open(*FILEH, $fname)) {
	    print STDERR "Couldn't open `$fname' for reading in \&MAKE_SOA().\n";
	    print STDERR "Error: $!\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	$_ = <FILEH>;
	if (/^\s*$/ || /^\s*;/ || /^\$ORIGIN\s+/) {
	    #
	    # We've encountered a zone file that was not created by
	    # us.  We'll deal with two possibilities.
	    #
	    if (/^;BIND DUMP/) {
		#
		# Not good.  This fits the profile of a dynamic zone
		# snapshot that BIND periodically dumps to disk.
		# 
		print STDERR "Dynamic zone format encountered in `$fname'.\n";
		print STDERR "I refuse to risk loss of data ... sorry.\n";
		exit(2);
	    } else {
		#
		# Some sites may want to copy zone files from a slave
		# name server and/or an AXFR query from a utility like DiG.
		# Deal with this possibility by skipping over the comment
		# lines and/or $ORIGIN directive that precede the SOA record.
		#
		while (/^\s*$/ || /^\s*;/ || /^\$ORIGIN\s+/) { $_ = <FILEH>; }
	    }
	}
	chop;
	if (/^\$TTL\s+([^;]+)/) {
	    $TTL_directive = $1;
	    $found_TTL_directive = 1;
	    if ($rfc2308 == 1 && $BIND_version_num == 0) {
		#
		# We are here because our RFC-2308 status was specifically
		# cancelled via the -o option *and* the FIXUP subroutine could
		# not determine the version of BIND on the master name server.
		# Under these two circumstances, the "$rfc2308" flag is set
		# to the "soft" value of 1.
		# Discovery of an existing $TTL directive overrides this
		# tentative condition and firmly establishes RFC-2308 status.
		# Now that this status is known, follow the same course
		# of action as documented by the comments in the relevant
		# section of FIXUP.
		#
		$rfc2308 = 2;
		$MasterTtl = $Ttl if $Ttl;
		$Ttl = $DefNegCache;
	    }
	    $_ = <FILEH>;
	    chop;
	}
	if (/\s\(\s*$/) {
	    unless ($soa_warned) {
		print STDOUT "Converting SOA format to new style.\n" if $verbose;
		$soa_warned = 1;
	    }
	    # The SOA record is split across more than one line.
	    # Although any combination is theoretically possible,
	    # only two variations occur in real life.  Either the
	    # SOA serial and timer fields are all on the next line
	    # or these fields appear individually on the next five
	    # lines.
	    #
	    $SOA_refresh = "";
	    $_ = <FILEH>;
	    if (/\s\)\s*$/) {
		#
		# The rest of the SOA RR has just been read.
		#
		($current_serial, $SOA_refresh, $SOA_retry,
		 $SOA_expire, $SOA_minimum, $tmp) = split(' ', $_, 6);
	    } else {
		#
		# All we have is the serial number so far.
		#
		($current_serial, $tmp) = split(' ', $_, 2);
	    }
	    if (!$SOA_refresh && (!$Refresh || !$Retry || !$Expire || !$Ttl)) {
		#
		# The rest of the SOA fields have not yet been obtained
		# and we need to preserve one or more SOA timer values.
		#
		($SOA_refresh, $tmp) = split(' ', <FILEH>, 2);
		($SOA_retry, $tmp) = split(' ', <FILEH>, 2);
		($SOA_expire, $tmp) = split(' ', <FILEH>, 2);
		($SOA_minimum, $tmp) = split(' ', <FILEH>, 2);
	    }
	    # Preserve existing SOA timer values in the absence
	    # of a replacement value passed via the -o/+t options.
	    #
	    $SOA_refresh = $Refresh if $Refresh;
	    $SOA_retry = $Retry if $Retry;
	    $SOA_expire = $Expire if $Expire;
	    $SOA_minimum = $Ttl if $Ttl;
	} else {
	    if (/^(.*?\s)SOA\s+(.+)/i) {
		$error = 0;
		$tmp = $1;
		$rdata = $2;
		$rdata =~ s/[()]//g;
		@SOA_fields = split(' ', $rdata);
		if ($#SOA_fields == 6) {
		    $current_serial = $SOA_fields[2];
		    $SOA_refresh = ($Refresh) ? $Refresh : $SOA_fields[3];
		    $SOA_retry = ($Retry) ? $Retry : $SOA_fields[4];
		    $SOA_expire = ($Expire) ? $Expire : $SOA_fields[5];
		    $SOA_minimum = ($Ttl) ? $Ttl : $SOA_fields[6];
		} else {
		    $error = 1;
		}
	    } else {
		$error = 1;
	    }
	    if ($error) {
		print STDERR "Improper format SOA in `$fname'.\n";
		print STDERR "I give up ... sorry.\n";
		exit(2);
	    }
	}
	unless (defined($NewSerial)) {
	    if ($current_serial == 4294967295) {
		#
		# Although the next serial number wraps around to zero,
		# skip this value to avoid potential interoperability
		# issues that different name server implementations may
		# have with an SOA serial number of zero.
		#
		$new_serial = 1;
	    } else {
		$new_serial = $current_serial + 1;
	    }
	} else {
	    ($new_serial, $limit_flag) = &INCREMENT_SERIAL($current_serial);
	}
	if ($rfc2308 == 2 && !$found_TTL_directive) {
	    #
	    # The existing zone file we just read did not have a $TTL
	    # directive but one will appear in the replacement zone file.
	    # This means that the SOA Minimum field will switch its
	    # context from a TTL value to a Negative Caching TTL value.
	    # Because existing SOA fields are preserved unless explicitly
	    # set via the -o/+t options, make sure that the SOA Minimum
	    # field is replaced by a suitable default value in the absence
	    # of one from -o/+t.
	    #
	    $SOA_minimum = ($Ttl) ? $Ttl : $DefNegCache;
	}
	close(*FILEH);
    } else {
	#
	# Since this is a new zone file, any valid serial number
	# can be assigned without having to do RFC-1982 arithmetic.
	#
	$new_serial = (defined($NewSerial)) ? $NewSerial : $DefSerial;
	$SOA_refresh = ($Refresh) ? $Refresh : $DefRefresh;
	$SOA_retry = ($Retry) ? $Retry : $DefRetry;
	$SOA_expire = ($Expire) ? $Expire : $DefExpire;
	if ($rfc2308 == 2) {
	    $SOA_minimum = ($Ttl) ? $Ttl : $DefNegCache;
	} else {
	    $SOA_minimum = ($Ttl) ? $Ttl : $DefTtl;
	}
    }

    unless (open(*FILEH, "> $fname")) {
	print STDERR "Couldn't open `$fname' for writing in \&MAKE_SOA()\n";
	print STDERR "Error: $!\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    }

    $SOA_mname = $RespHost;
    $SOA_rname = $RespUser;
    if ($uq_fname eq $Domainfile) {
	#
	# Make a cosmetic indulgence by keeping in-domain names
	# relative to the origin in the forward-mapping file.
	#
	$SOA_mname =~ s/$Domainpattern\.$//i;
	$SOA_rname =~ s/$Domainpattern\.$//i;
    }
    if ($need_numeric_ttl) {
	$SOA_refresh = &SECONDS($SOA_refresh);
	$SOA_retry   = &SECONDS($SOA_retry);
	$SOA_expire  = &SECONDS($SOA_expire);
	$SOA_minimum = &SECONDS($SOA_minimum);
    } else {
	$SOA_refresh = &SYMBOLIC_TIME($SOA_refresh);
	$SOA_retry   = &SYMBOLIC_TIME($SOA_retry);
	$SOA_expire  = &SYMBOLIC_TIME($SOA_expire);
	$SOA_minimum = &SYMBOLIC_TIME($SOA_minimum);
    }
    if ($rfc2308 == 2) {
	$TTL_directive = $MasterTtl if $MasterTtl;
	$TTL_directive = $DefTtl unless $TTL_directive;
	$TTL_directive = ($need_numeric_ttl) ? &SECONDS($TTL_directive)
					     : &SYMBOLIC_TIME($TTL_directive);
	print FILEH "\$TTL $TTL_directive\n";
	print FILEH "\@\tSOA\t$SOA_mname $SOA_rname";
    } else {
	#
	# If no $TTL directive is to be written, RFC-1035 requires the first
	# record of the zone file to have an explicitly-specified TTL field.
	#
	printf FILEH "\@ %5s SOA\t%s %s", $SOA_minimum, $SOA_mname, $SOA_rname;
    }
    print FILEH " ( $new_serial $SOA_refresh $SOA_retry $SOA_expire $SOA_minimum )\n";
    foreach $s (@FullServers) {
	$tmp = $s;
	$tmp =~ s/$Domainpattern\.$//i if $uq_fname eq $Domainfile;
	print FILEH "\tNS\t$tmp\n";
    }
    if (exists($PartialServers{$uq_fname})) {
	foreach $s (split(' ', $PartialServers{$uq_fname})) {
	    $tmp = $s;
	    $tmp =~ s/$Domainpattern\.$//i if $uq_fname eq $Domainfile;
	    print FILEH "\tNS\t$tmp\n";
	}
    } elsif (!@FullServers) {
	#
	# Add name server in MNAME field of SOA record if missing -s/-S
	#
	print FILEH "\tNS\t$SOA_mname\n";
    }
    if ($uq_fname eq $Domainfile && keys(%Apex_RRs)) {
	#
	# Add additional records from the -T option.  These RRs
	# have already been submitted to READ_RRs() for validation
	# and registration into the appropriate data structures.
	#
	foreach $rrtype (keys %Apex_RRs) {
	    foreach $data (@{ $Apex_RRs{$rrtype} }) {
		$rdata = $data;
		if ($rdata =~ /\n$/) {
		    #
		    # A newline appended to the "$rdata" string is a
		    # data structure signal to indicate that this is
		    # a continuation line of a multi-line record.
		    #
		    $rdata =~ s/\n$//;
		    if ($rdata =~ /\n$/) {
			#
			# Besides this being a continuation line, a second
			# appended newline signifies that the previous line
			# ended with an open quote in effect.  Therefore,
			# the usual cosmetic indentation must not be added
			# in order to maintain data integrity.
			#
			print FILEH $rdata;
		    } else {
			print FILEH "\t\t$rdata\n";
		    }
		} else {
		    ($ttl, $rdata) = split(/,/, $rdata, 2);
		    $ttl = ($need_numeric_ttl) ? &SECONDS($ttl)
					       : &SYMBOLIC_TIME($ttl);
		    printf FILEH " %6s %s\t%s\n", $ttl, $rrtype, $rdata;
		}
	    }
	}
    }
    print FILEH "\n";
    $DB_filehandle{*FILEH} = $fname;
    &CLOSE(*FILEH);		# CLOSE() will begin the management of %LRUtable

    unless ($limit_flag) {
	return 1;
    } elsif ($limit_flag == 1) {
	($message = <<EOT) =~ s/^\s+\|//gm;
	|Warning: SOA serial number for db file `$uq_fname' was already
	|         set to the requested value of $NewSerial.  It has been
	|         incremented to $new_serial instead.
EOT
	print STDERR "$message\n";
	return 1;
    } else {
	($message = <<EOT) =~ s/^\s+\|//gm;
	|Warning: SOA serial increment from $current_serial to $NewSerial for db file
	|         `$uq_fname' exceeds the RFC-1982 maximum of 2147483647.
	|         To prevent zone propagation failures, only the maximum increment
	|         was applied; the serial number is set to $new_serial instead.
EOT
	print STDERR "$message\n";
	return 0;
    }
}


#
# Initialize database files with new or updated SOA records.
#
sub INITDBs {
    my $LoopbackSOA_incremented = 0;
    my $warning = 0;
    my ($FILEH, $alias, $file_name, $lc_alias, $message, $ttl, $zone_entry);

    foreach $zone_entry (@makesoa) {
	($file_name, $FILEH) = split(' ', $zone_entry, 2);
	$LoopbackSOA_incremented = 1 if $file_name eq "db.127.0.0";
	unless (&MAKE_SOA("$Search_dir/$file_name", $file_name, $FILEH)) {
	    $warning = 1
	}
    }
    unless (exists($RRowners{localhost})) {
	#
	# Since the -T option argument 'ALIAS=localhost' was not specified,
	# make the address record for `localhost' appear as the first
	# non-top-of-zone-related RR in the forward-mapping zone for "$Domain".
	#
	&PRINTF(*DOMAIN, "%s\tA\t127.0.0.1\n", "localhost\t");
	$RRowners{localhost} = " A ";
    }
    if (keys(%Apex_aliases)) {
	#
	# Add any CNAMEs that were configured with the `ALIAS=' keyword
	# of the -T option.  These always point to the zone apex.
	# NOTE: These aliases have already been registered in the
	#       "%RRowners" hash when they were submitted to
	#       READ_RRs() for validation by FIXUP().
	#
	while (($lc_alias, $alias) = each %Apex_aliases) {
	    ($alias, $ttl) = split(' ', $alias, 2);
	    if ($need_numeric_ttl) {
		$ttl = &SECONDS($ttl);
	    } else {
		$ttl = &SYMBOLIC_TIME($ttl);
	    }
	    &PRINTF(*DOMAIN, "%s%s\tCNAME\t\@\n", &TAB($alias, 16), $ttl);
	}
    }
    if ($MakeLoopbackSOA) {
	$FILEH = "DB.127.0.0";
	unless ($LoopbackSOA_incremented) {
	    $file_name = "db.127.0.0";
	    $warning = 1 unless &MAKE_SOA("$Search_dir/db.127.0.0", $file_name,
					  $FILEH);
	}
	&PRINTF($FILEH, "1\t\tPTR\tlocalhost.\n");
    }
    if ($warning) {
	($message = <<EOT) =~ s/^\s+\|//gm;
	|
	|    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	|    !!                                                              !!
	|    !! IMPORTANT: Do *not* run this program again until all of the  !!
	|    !!            zones affected by the SOA serial number warnings  !!
	|    !!            have propagated to *all* of their configured      !!
	|    !!            (both delegated and stealth) slave name servers.  !!
	|    !!            Failure to do so might cause some slaves to treat !!
	|    !!            their SOA serial numbers as being "greater" than  !!
	|    !!            the effective serial number on the master server. !!
	|    !!                                                              !!
	|    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
EOT
	print STDERR "$message\n";
    }
}


#
# Take a Left-Hand-Side or Right-Hand-Side template specification
# passed via the BIND $GENERATE directive and return a specially-
# formatted string.  This subroutine's caller, READ_RRs(), will
# then generate the appropriate owner name/RDATA fields by using
# the eval() function on the returned LHS and RHS strings.
#
# Return values:
#    "eval_string": successful $GENERATE template -> eval() string conversion
#            undef: invalid $GENERATE template specification encountered
#
sub GEN_NAME {
    my ($template) = @_;
    my ($eval_string, $prefix, $suffix, $offset, $width, $radix);

    $eval_string = '""';
    $template =~ s/\$\$/\\\$/g;	    # "$$" -> "\$" for backwards compatibility
    while ($template) {
	unless ($template =~ /([^\$]+)?\$(.*)/) {
	    #
	    # We are finished - there are no more "$" characters.
	    # Append what's left of "$template" to "$eval_string"
	    # and set "$template" to null so that the loop exits.
	    #
	    $eval_string .= " . \"$template\"";
	    $template = "";
	} else {
	    #
	    # A "$" character exists.  Append whatever appears in front
	    # of it to "$eval_string".  "$template" is re-assigned with
	    # whatever appears after the "$" character.
	    # 
	    $prefix = (defined($1)) ? $1 : "";
	    $suffix = (defined($2)) ? $2 : "";
	    $eval_string .= " . \"$prefix\"";
	    $template = $suffix;
	    if ($eval_string =~ /\\\"$/) {
		#
		# It turns out that an escape character preceded the "$"
		# that was just found thus rendering it as just another
		# character.
		# Replace the escape character with the "$" character that
		# was removed from the end of the "$eval_string" variable
		# and proceed to the next loop iteration.
		#
		$eval_string =~ s/\\\"$/\$\"/;
	    } else {
		#
		# The "$" character is indeed an iterator replacement symbol.
		# Now see if it is followed by a format specification.
		#
		unless ($template =~ /^{/) {
		    #
		    # No format specification appears after the "$" character.
		    # Substitute an appended literal iterator variable "$i"
		    # which will be used by READ_RRs() in place of the "$"
		    # iteration token of the BIND $GENERATE template.
		    #
		    $eval_string .= ' . $i';
		} elsif ($template =~ /^{(-?\d+),*([^,}]+)?,*([^,}]+)?}(.*)/) {
		    #
		    # A valid format specification appears after the "$"
		    # substitution symbol, e.g.,  host${0,3,d}
		    # NOTE: As of version 9.2.0, BIND doesn't complain if
		    #       the `width' and `radix' fields do not consist of
		    #       digits and the pattern [doxX], respectively, i.e.,
		    #       host${9,foo,zoo} silently defaults to host${9,0,d}.
		    #
		    $offset = $1;
		    $width = $2;
		    $radix = $3;
		    $template = $4;
		    unless (defined($width) && $width =~ /^\d+$/) {
			$width = 0;
		    } else {
			$width = "0$width";	# prepend "0" for zero-padding
		    }
		    $radix = "d" unless defined($radix) && $radix =~ /^[doxX]$/;
		    $eval_string .= " . sprintf(\"%${width}${radix}\", ";
		    $eval_string .= '($i + ' . "$offset))";
		} else {
		    #
		    # The BIND $GENERATE template specification is invalid.
		    #
		    return;
		}
	    }
	}
    }
    # To keep things simple, the template translator always precedes
    # a data substring with the "." concatenation character before
    # appending it to "$eval_string".  This results in one or more
    # consecutive NO-OP concatenations ("" . ) at the start of each
    # "$eval_string".  Remove them to eliminate unnecessary processing
    # by eval() in READ_RRs().
    #
    1 while $eval_string =~ s/^\"\" . //;
    return $eval_string;
}


#
# Scan `spcl' files (or zone transfer [AXFR] data if in verify mode)
# so that the following may be done:
#   * Store information about in-domain owner names in order to prevent
#     (detect in verify mode) the creation (existence) of conflicting
#     CNAMEs (forward-mapping files only).
#   * Store information about the RDATA fields of NS, MX, PTR, SRV, RT,
#     and AFSDB records for later reporting in case these RRs are found
#     to be not pointing to domain names with A records.
#   * Store information about the TXTDNAME field of RP RRs so that
#     missing TXT records can be identified.
#   * Store information about the RDATA field of CNAME RRs so that
#     dangling CNAMEs can be identified.
#   * Keep track of wildcard RRs and child domains so that the
#     auditing of the various record types can be done properly.
#   * Report RFC-1123 errors with owner-names and RDATA domain names plus
#     errors with malformed IP addresses.
#
# Return values:
#   0 = no warnings
#   1 = warnings
#   2 = file open error
#
sub READ_RRs {
    my ($spcl_file, $zone, $origin, $owner, $warning_status) = @_;
    my ($bad_record, $char, $continuation_line, $data, $data2, $default_ttl);
    my ($domain_pattern, $error, $gen_file, $gen_owner, $gen_rdata, $i);
    my ($include_file, $last_char, $lhs, $line_buffer, $line_num, $message);
    my ($n, $new_origin, $open_paren_count, $open_quote, $original_line);
    my ($port, $preference, $protocol, $range, $rdata, $rp_mailbox);
    my ($rr, $rrtype, $service, $spcl_name, $split_line_num, $start, $step);
    my ($stop, $tmp, $ttl, $txt_domain, $uq_owner, $weight, $zone_pattern);
    my $zone_suffix;
    local (*SPCL, *GEN);

    unless (&OPEN(*SPCL, $spcl_file)) {
	if ($verbose) {
	    print STDERR "\n" unless $newline_printed;
	    print STDERR "Couldn't open `$spcl_file': $!";
	    #
	    # The terminating newline will be output
	    # by this subroutine's caller.
	}
	$Load_Status = 3;
	$warning_status = $newline_printed = 2;
	return $warning_status;
    }
    $zone = lc($zone);			  # in case the -P option is in effect
    $origin = lc($origin);		  # ditto
    $domain_pattern = lc($Domainpattern); # time-saver to accommodate -P option
    $zone_suffix = ($origin ne '.') ? ".$origin" : ".";
    $zone_pattern = $zone;
    #
    # Make sure to escape any Regular Expression metacharacters
    # that may be present in "$zone_pattern".
    #
    $zone_pattern =~ s/([.\|\\\$\^\+\[\(\)\?'`])/\\$1/g;
    $zone_pattern = "\\.$zone_pattern" if $zone ne '.';
    if ($spcl_file eq "$debug_DIR/-T_option_RRs") {
	$spcl_name = "-T option";
    } else {
	($spcl_name = $spcl_file) =~ s/^.*\///;
	$spcl_name = "`$spcl_name'";
    }
    $message = $ttl = "";
    $continuation_line = $open_paren_count = $open_quote = 0;
    if (defined($MasterTtl)) {
	$default_ttl = lc($MasterTtl);
    } elsif (defined($Ttl)) {
	$default_ttl = lc($Ttl);
    } else {
	$default_ttl = lc($DefTtl);
    }
    $line_num = 0;
    #
    # The following code block for reading text from a master zone data
    # file and assembling that data into DNS records emulates the strict
    # behavior of the BIND 9 lexer.  DNS records which are not compliant
    # with the syntax specifications of RFC-1035 will be flagged as errors
    # and skipped from further processing.
    # Illegal zone file syntax that the lexers in BIND 4/8 mistakenly
    # allowed must now be fixed.
    #
    while (<SPCL>) {
	$line_num++;
	if ($continuation_line || /["()]/) {
	    #
	    # Scan the line character-by-character to identify the
	    # proper context of any quoting characters we find.
	    #
	    unless ($continuation_line) {
		$original_line = $_;
		$line_buffer = "";
		$split_line_num = $line_num;
		$bad_record = 0;
	    } else {
		#
		# As a sanity check for unbalanced quotes/parentheses,
		# keep track of the accumulated length of the concatenated
		# continuation lines and quit reading the zone file if it
		# exceeds a certain threshold.
		# This threshold number was determined by deliberately
		# breaking a zone file and submitting it to the BIND 9
		# utility `named-checkzone'.  The isc_lex_gettoken()
		# call fails with a "ran out of space" error after
		# running through about 131500 bytes (approximately
		# 3000 lines of a typical zone file).
		#
		$original_line .= $_;
		last if length($original_line) > 131500;
	    }

	    # Scan each character in the line while keeping in mind
	    # the following special characters and quoting hierarchy:
	    #
	    #   '\'  ->  An escape (backslash) cancels any special meaning
	    #            of the character that immediately follows.  This
	    #            includes backslashes, double-quotes, semicolons,
	    #            left and right parentheses, and the newline.
	    #
	    #   '"'  ->  The double-quote character quotes whitespace, the
	    #            ";()" characters which are special to RFC-1035,
	    #            and escaped newlines until a matching unescaped
	    #            double-quote is reached.
	    #
	    #   ';'  ->  Signifies the start of a comment.  It and
	    #            the remaining characters that follow are
	    #            ignored up to and including the next escaped
	    #            or unescaped newline character.
	    #
	    #   '('  ->  Signifies that subsequent newline characters
	    #            are to be ignored, i.e., quoted, if encountered.
	    #            Does not perform any other quoting function.
	    #            May be nested to multiple levels.
	    #
	    #   ')'  ->  Cancels the effect of the "(" character at
	    #            the current nesting level.  Newlines are still
	    #            ignored until the outer-most opening "(" is
	    #            balanced by the corresponding ")" character.
	    #
	    #   1. An escaped newline is only valid within matching
	    #      double-quote characters.  The lexer will report
	    #      an "unexpected end of input" error otherwise.
	    #
	    #   2. An unescaped newline effectively cancels an open
	    #      double-quote character.  The lexer will report an
	    #      "unbalanced quotes" error if this situation occurs.
	    #      If, however, there are also one or more open parentheses
	    #      in effect, the lexer will continue to scan for their
	    #      closing ")" counterparts to try to complete the disposition
	    #      of the defective record.  Quoting will be cancelled and
	    #      not be toggled by subsequent double-quote characters until
	    #      the balancing parentheses are found.
	    #
	    #   3. If the nesting level of parentheses goes negative, the
	    #      lexer will immediately report the imbalance and discard
	    #      the rest of the line.  If an odd number of double-quote
	    #      characters are part of the refuse, this may have a
	    #      side-effect of introducing an "unbalanced quotes" error
	    #      in a subsequent line.  Since resynchronization has to
	    #      occur at some point, however, the lexer's chosen priority
	    #      is to balance parentheses.
	    #
	    chop;
	    $last_char = "";	  # don't carry an escape from the previous line
	    while (length($_)) {
		($char, $_) = split(//, $_, 2);
		if ($char eq "\\" && $last_char eq "\\") {
		    #
		    # An escape character which is itself escaped
		    # becomes an ordinary backslash character.
		    # Move it into the buffer and remove its ability
		    # to escape the next character in the byte stream.
		    #
		    $line_buffer .= $char;
		    $last_char = "";
		    next;
		}
		if ($char eq '"' && $last_char ne "\\") {
		    $open_quote = !$open_quote unless $bad_record;
		    $line_buffer .= $char;
		    $last_char = $char;
		    next;
		}
		unless ($open_quote || $last_char eq "\\") {
		    #
		    # Encountering an unquoted and unescaped semicolon
		    # marks the start of a comment.  There is no need
		    # to scan the rest of the line. 
		    # 
		    last if $char eq ";";
		    #
		    # Unquoted and unescaped parentheses are not part of
		    # the DNS resource record but an RFC-1035 construct
		    # to ignore intervening newlines.
		    # Keep track of them to maintain the current nesting
		    # level but do not include these characters in the
		    # line buffer of the record that we are assembling.
		    #
		    if ($char eq "\(") {
			$open_paren_count++;
			next;
		    }
		    if ($char eq "\)") {
			$open_paren_count--;
			last if $open_paren_count < 0;
			next;
		    }
		}
		if ($open_quote) {
		    if ($char eq "\\" && $last_char ne "\\" && !length($_)) {
			#
			# An escaped newline has been encountered.
			# Replace the backslash with a newline so
			# it can be converted to BIND 9 presentation
			# format in the next block.
			#
			$char = "\n";
		    }
		    if (ord($char) < 32) {
			#
			# Adopt the BIND 9 presentation format in which
			# quoted non-printing characters other than a
			# space get converted into an escape character
			# followed by the non-printing character's
			# three-digit decimal equivalent ASCII value.
			# An escaped newline followed by a tab, for
			# example, would appear as "\010\009".
			#
			$line_buffer .= "\\0";
			$line_buffer .= "0" if ord($char) < 10;
			$line_buffer .= ord($char);
		    } else {
			$line_buffer .= $char;
		    }
		    $last_char = $char;
		} else {
		    #
		    # Preservation of cosmetic whitespace is unnecessary
		    # since the assembled record will be parsed again into
		    # its DNS components, i.e., the owner, TTL, class,
		    # RRtype, and RDATA fields.
		    #
		    $char = " " if $char eq "\t";
		    unless ($char eq " " && $last_char eq " ") {
			$line_buffer .= $char;
			$last_char = $char;
		    }
		}
	    }
	    # Assess the situation now that the character scan
	    # of the current line is complete.
	    #
	    if ($open_paren_count < 0) {
		print STDERR "Unbalanced parentheses; file `$spcl_file', line $line_num\n";
		print STDERR "> $original_line";
		$Load_Status = 3;
		$continuation_line = $open_paren_count = 0;
		next;
	    }
	    if ($open_quote && $last_char ne "\n") {
		print STDERR "Unbalanced quotes; file `$spcl_file', line $line_num\n";
		print STDERR "> $original_line";
		$open_quote = 0;
		$Load_Status = 3;
		$bad_record = 1 if $open_paren_count;
		next;
	    }
	    $continuation_line = $open_quote + $open_paren_count;
	    next if $continuation_line || $bad_record;
	    $_ = $line_buffer;
	    next if /^\s*$/;			# line was only a comment 
	} else {
	    #
	    # Lex the much simpler case of an ordinary line of text.
	    #
	    next if /^\s*$/ || /^\s*;/;
	    $original_line = $_;
	    s/([^\\]);.*/$1/;			# strip comments
	    if (/\\$/) {
		#
		# Escaped newlines are only valid when quoted.
		#
		print STDERR "Unexpected end of input; file `$spcl_file', line $line_num\n";
		print STDERR "> $original_line";
		$Load_Status = 3;
		next;
	    }
	}
	# The RR parsing pattern expects the RRtype field to be delimited
	# by whitespace at both ends.  A missing RDATA field (normally an
	# error but valid for APL RRs [RFC-3123]) will prevent the RR from
	# matching the pattern unless we make it a practice to always append
	# a space character to each line as it emerges from the lexer.
	# The side-effect of this is that the parsed RDATA field *must*
	# always be chopped of *all* trailing whitespace in order to be
	# properly processed.
	#
	$_ .= " ";
	$rr = 0;
	if (/^[^\$]/ && /^(.*?\s)($RRtypes)\s+(.*)/io) {
	    #                ^                      ^
	    # Note: Minimal matching must be used in the first group of the
	    #       Regular Expression.  Otherwise, the following SOA record
	    #       will be mistakenly matched as an NS record:
	    #
	    #       @      1D IN SOA       ns hostmaster ( 123 3h 1h 1w 10m )
	    #                              ^^
	    #       Also, "$RRtypes" is static during the program's execution.
	    #       Make sure the "compile once" pattern modifier is in place.
	    #
	    $tmp = lc($1);
	    $rrtype = uc($2);
	    ($rdata = lc($3)) =~ s/\s+$//;		      # chop whitespace
	    $rr = 1;
	    $tmp =~ s/([^\\])\s+(in|hs|ch(aos)?|any)\s+/$1 /; # strip class
	    if ($tmp =~ /^\S/) {			      # TTL *may* exist
		($owner, $ttl) = split(' ', $tmp);
		$ttl = $default_ttl unless $ttl;
		#
		# Make sure that all new owner names are fully-qualified.
		#
		1 while $owner =~ s/\\\\/\\/g;		# strip excess escapes
		$owner = $origin if $owner eq '@';
		$owner .= $zone_suffix unless $owner =~ /(^|[^\\])\.$/;
	    } elsif ($tmp =~ /\S/) {			# TTL field does exist
		($ttl = $tmp) =~ s/\s+//g;
	    } else {					# TTL field is null
		$ttl = $default_ttl;
	    }
	    if ($owner =~ /$zone_pattern$/ || $owner eq $zone) {
		#
		# Only consider RRs matching the current zone tree.  RRs in
		# child zones will also be processed so that missing glue
		# and/or non-glue records can be reported.
		#
		# Normal mode:
		# ------------
		# Register owner names that are in the forward-mapping
		# domain so that conflicting CNAMEs won't be created.
		# The idea here is that the owner names of already-existing
		# RRs should have priority over potential CNAMEs that have
		# yet to be discovered in the host table.
		#
		# Verify mode:
		# ------------
		# Register owner names as the zone file is read so that
		# CNAME conflicts can be reported.  In both normal mode
		# and verify mode, registered CNAMEs will be used to
		# quickly determine if they are pointed to by the RDATA
		# field of any record type (NS, MX, etc.) that should
		# properly point to the canonical domain name instead.
		#
		$uq_owner = $owner;
		$uq_owner =~ s/$domain_pattern\.$// unless $owner eq '.';
		if ($rrtype eq 'CNAME' && $owner eq $zone) {
		    $message = "Warning: Zone name can not exist as a CNAME";
		    $Load_Status = 3;
		} elsif ($rrtype eq 'NS' && $owner =~ /^\*(\.|$)/) {
		    $message = "Warning: NS owner name can not exist as a wildcard";
		    $Load_Status = 3;
		} elsif (exists($RRowners{$uq_owner})) {
		    #
		    # See if there's a "CNAME and other data" error.
		    # Allow for the fact that the DNSSEC RRtypes SIG, NXT,
		    # and KEY are allowed to share owner names with CNAMEs
		    # per RFC-2065.
		    #
		    $data = $data2 = $RRowners{$uq_owner};
		    if ($rrtype eq 'CNAME') {
			#
			# Remove the DNSSEC RR types from the temporary copy
			# of the accumulated RR types for this owner name.
			# Any leftover RR types will trigger the warning.
			#
			1 while $data2 =~ s/ (SIG|NXT|KEY) / /;
		    }
		    if ($data =~ / CNAME / && $rrtype !~ /^(SIG|NXT|KEY)$/) {
			$message = "Warning: `$uq_owner' already exists as a CNAME";
			$Load_Status = 3;
		    } elsif ($rrtype eq 'CNAME' && $data2 ne " ") {
			$message = "Warning: `$uq_owner' already exists as another resource record";
			$Load_Status = 3;
		    }
		    if ($data !~ / $rrtype /) {
			#
			# If necessary, add the RR type to those
			# already registered to the owner name.
			#
			$RRowners{$uq_owner} .= "$rrtype ";
		    }
		} else {
		    #
		    # Register the new owner name and its RR type.
		    #
		    $RRowners{$uq_owner} = " $rrtype ";
		    #
		    # Make sure to trim any terminating root zone
		    # character before submitting the owner field
		    # to the CHECK_NAME subroutine.  Wildcard labels
		    # of A, MX, and WKS RRs will also be removed.
		    # NOTE: Use the FQDN to make sure that the owner names
		    #       of RRtypes which are subject to stricter name
		    #       checking get passed to CHECK_NAME in their entirety.
		    #
		    ($tmp = $owner) =~ s/\.$//;
		    $tmp =~ s/^\*(\.|$)// if $rrtype =~ /^(A|MX|WKS)$/;
		    $error = ($tmp) ? &CHECK_NAME($tmp, $rrtype) : 0;
		    if ($error) {
			$message .= "Invalid owner name field";
			$Load_Status = $error if $error > $Load_Status;
		    }
		    $data = ($error == 3) ? "" : $uq_owner;
		    while ($data =~ /(\\[.]|[^.])*\./) {
			#
			# The unqualified owner consists of two or more labels.
			# Register the interior labels in the %RRowners hash
			# so that we can correctly distinguish between a
			# non-existent domain name and a domain name with no
			# DNS resource records during the auditing phase.
			#
			$data =~ s/(\\[.]|[^.])*\.//;	# strip leading label
			$RRowners{$data} = " " unless exists($RRowners{$data});
		    }
		    if ($rrtype eq 'SRV' && $rfc2782) {
			#
			# Although the owner fields of SRV RRs are not
			# subject to RFC-1123 name checking, we'll use
			# the presence of another flag, "$rfc2782", to
			# make sure that the owner field complies with
			# RFC-2782:
			#
			#   1. The Service and Protocol labels are present.
			#   2. Each label begins with an underscore character.
			#
			# BIND does not enforce these specifications, however,
			# since RFC-2915 (the NAPTR RR) requires that the owner
			# field of an SRV RR also exist as an ordinary domain
			# name to which a NAPTR record can point so that a
			# client can obtain the information in the RDATA field
			# of the SRV record.
			#
			# Because of these two possible query contexts, BIND
			# has no other choice than to go with the lowest common
			# denominator and leave the special naming requirements
			# of RFC-2782 unenforced.
			#
			# However, a DNS administrator will presumably know the
			# query context of any SRV RRs in the zone data under
			# his/her control.  Therefore, selective enforcement of
			# RFC-2782 is available via the "$rfc2782" flag that is
			# set with the -I rfc2782 option.
			#
			if ($owner eq $zone) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}Missing SRV Service and Protocol labels";
			} else {
			    ($tmp = $uq_owner) =~ s/([^\\])[.]/$1 /;
			    ($service, $protocol) = split(' ', $tmp, 2);
			    unless ($service =~ /^_.+/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Leading underscore character missing from SRV Service label";
			    }
			    unless ($protocol) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Missing SRV Protocol label";
			    } elsif ($protocol !~ /^_.+/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Leading underscore character missing from SRV Protocol label";
			    }
			}
		    }
		}
		if ($ttl && $ttl !~ /^(\d+|(\d+[wdhms])+)$/) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Invalid TTL value";
		    $Load_Status = 3;
		}
		# Parse and process the RDATA field(s) of the various
		# resource record types that will help in the effort
		# to identify the most common types of configuration
		# errors.  Even if the "$audit" flag is false, the
		# auditing data structures identify new domain names
		# in the RDATA fields and thus help to minimize
		# redundant calls to the CHECK_NAME subroutine.
		# For best efficiency, the processing blocks should be
		# arranged so that the most common RR types appear first.
		# NOTE: In order to conserve memory resources of the
		#       audit-related data structures, domain names are
		#       stored in zone-relative format whenever possible.
		#
		1 while $rdata =~ s/\\\\/\\/g;		# strip excess escapes
		if ($rrtype eq 'MX') {
		    ($preference, $tmp) = split(' ', $rdata, 2);
		    $rdata = (defined($tmp)) ? $tmp : "";
		    unless ($preference =~ /^\d+$/ && $preference <= 65535) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid MX Preference value";
			$Load_Status = 3;
		    }
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($MXlist{$rdata})) {
			    $MXlist{$rdata} .= ", $spcl_name" unless $MXlist{$rdata} =~ /$spcl_name/;
			    if ($owner eq $zone) {
				#
				# The reason for checking only the zone apex
				# for redundantly-specified target hostnames
				# of certain routing RRs (MX and RT) is a
				# trade-off.  Complete disclosure could result
				# in the same basic message repeated one or more
				# times for every domain name in a zone plus the
				# memory requirement of storing all the domain
				# names in yet another hash besides %RRowners.
				# Instead, we'll make the reasonably probable
				# assumption that the zone apex has an MX and/or
				# RT RRset that is similar to those throughout
				# the rest of the zone.
				#
				if (exists($Apex_route_RRs{MX})
				    && exists($Apex_route_RRs{MX}{$rdata})) {
				    $n = ($message) ? ".\n" : "";
				    $message .= "${n}Redundant MX hostname";
				    $Load_Status = 1;
				    $Apex_route_RRs{MX}{$rdata} .= " $preference";
				} else {
				    $Apex_route_RRs{MX}{$rdata} = $preference;
				}
			    }
			} else {
			    $MXlist{$rdata} = "$spcl_name";
			    if ($owner eq $zone) {
				$Apex_route_RRs{MX}{$rdata} = $preference;
			    }
			    #
			    # Zone names, i.e., the owner fields of SOA records,
			    # are not subject to RFC-1123 name-checking.  This
			    # is no problem if the zone only contains other
			    # RRtypes which also have less-strict name checking.
			    # For RRtypes which have stricter name checking,
			    # however, make sure the check is contextually
			    # complete by using the FQDN.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'MX') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid MX hostname field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    } elsif ($rdata) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid MX hostname field";
			$Load_Status = 3;
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Missing MX hostname field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'A') {
		    if ($rdata =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
			if (($4 > 255 || $3 > 255 || $2 > 255 || $1 > 255)
			    || $rdata =~ /(^|\.)0\d/) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}Invalid IP address";
			    $Load_Status = 3;
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid IP address";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'CNAME') {
		    #
		    # Keep track of CNAME references so that dangling
		    # CNAMEs and CNAME loops can be detected.
		    #
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if ($rrtype eq 'CNAME' && $uq_owner eq $rdata) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}Warning: `$rdata' points back to itself";
			}
			if (exists($spclCNAME{$rdata})) {
			    $spclCNAME{$rdata} .= ", $spcl_name" unless $spclCNAME{$rdata} =~ /$spcl_name/;
			} else {
			    $spclCNAME{$rdata} = "$spcl_name";
			    ($tmp = $rdata) =~ s/[^\\]\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'CNAME') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid RDATA field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RDATA field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'PTR') {
		    #
		    # Keep track of PTR references to determine whether
		    # or not they properly point to Address RRs.
		    #
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($spclPTR{$rdata})) {
			    $spclPTR{$rdata} .= ", $spcl_name" unless $spclPTR{$rdata} =~ /$spcl_name/;
			} else {
			    $spclPTR{$rdata} = "$spcl_name";
			    #
			    # Since the RDATA field must properly point
			    # to a canonical domain name, enforce a
			    # stricter level of checking than was done
			    # on the PTR record's owner field.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid RDATA field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RDATA field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'RP') {
		    if ($rdata =~ /^(\S+)\s+(\S+)$/) {
			$rp_mailbox = $1;
			$txt_domain = $2;
			$rp_mailbox = $origin if $rp_mailbox eq '@';
			$txt_domain = $origin if $txt_domain eq '@';
			#
			# Do not append/strip the domain if the
			# MAILBOX/TXTDNAME field ends with/exists as
			# a "." character.
			#
			$rp_mailbox .= $zone_suffix unless $rp_mailbox =~ /(^|[^\\])\.$/;
			$txt_domain =~ s/$domain_pattern\.$// unless $txt_domain eq ".";
			#
			# Starting with BIND 4.9.4, `res_mailok' in `res_comp.c'
			# states that RNAMEs of SOA and RP RRs "can have any
			# printable character in their first label, but the
			# rest of the name has to look like a host name."
			# Also, an RNAME of "." is a valid symbol for a missing
			# representation.  These checks will be made now.
			#
			if ($rp_mailbox ne ".") {
			    $tmp = $rp_mailbox;
			    $tmp =~ s/(\\[.]|[^.])*\.//;    # strip first label
			    $tmp =~ s/\.$//;		    # strip last dot
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 1;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid MAILBOX field in RP RR";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
			if ($txt_domain ne '.') {
			    if (exists($spclRP{$txt_domain})) {
				$spclRP{$txt_domain} .= ", $spcl_name" unless $spclRP{$txt_domain} =~ /$spcl_name/;
			    } else {
				$spclRP{$txt_domain} = "$spcl_name";
				($tmp = $txt_domain) =~ s/[^\\]\.$//;
				$error = &CHECK_NAME($tmp, 'TXT');
				if ($error) {
				    $n = ($message) ? ".\n" : "";
				    $message .= "${n}Invalid TXTDNAME field in RP RR";
				    $Load_Status = $error if $error > $Load_Status;
				}
			    }
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid number of RDATA fields in RP RR";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'NS' && $owner !~ /^\*(\.|$)/) {
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($NSlist{$rdata})) {
			    $NSlist{$rdata} .= ", $spcl_name" unless $NSlist{$rdata} =~ /$spcl_name/;
			} else {
			    $NSlist{$rdata} = "$spcl_name";
			    #
			    # Since the RDATA field must properly point
			    # to a canonical domain name, enforce a
			    # stricter level of checking than was done
			    # on the NS record's owner field.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid RDATA field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
			if ($audit) {
			    if ($verify_mode && $recursive_verify
				&& $owner ne $zone && !exists($NSowners{$uq_owner})) {
				#
				# This subzone will be the next one to be
				# verified unless a subsequent subzone is
				# encountered.
				#
				push(@Vdomains, $owner);
			    }
			    # Store the owner, TTL, and rdata fields of the NS
			    # resource records.  The owner fields will allow
			    # out-of-zone data to be recognized.  The NS RRsets
			    # for each domain will be checked for having at
			    # least two listed name servers (RFC-1034) and
			    # consistent TTL values (RFC-2181).  Checks for
			    # necessary glue records as well as non-glue at or
			    # below any zone cut will also be made.
			    # If we are verifying a domain, the NS records will
			    # be supplied as input data to the `check_del'
			    # program so that proper delegation can be checked.
			    #
			    $NSowners{$uq_owner} .= " " . &SECONDS($ttl) . " $rdata";
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RDATA field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'SRV') {
		    ($preference, $weight, $port, $tmp) = split(' ', $rdata, 4);
		    $rdata = (defined($tmp)) ? $tmp : "";
		    unless ($preference =~ /^\d+$/ && $preference <= 65535) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid SRV Priority value";
			$Load_Status = 3;
		    }
		    unless (defined($weight) && $weight =~ /^\d+$/ && $weight <= 65535) {
			$n = ($message) ? ".\n" : "";
			$tmp = (defined($weight)) ? "Invalid" : "Missing";
			$message .= "${n}$tmp SRV Weight value";
			$Load_Status = 3;
		    }
		    unless (defined($port) && $port =~ /^\d+$/ && $port <= 65535) {
			$n = ($message) ? ".\n" : "";
			$tmp = (defined($port)) ? "Invalid" : "Missing";
			$message .= "${n}$tmp SRV Port value";
			$Load_Status = 3;
		    }
		    unless ($rdata) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Missing SRV Target field";
			$Load_Status = 3;
		    } elsif ($rdata !~ /^\S+$/) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid SRV Target field";
			$Load_Status = 3;
		    } elsif ($rdata !~ /^\.$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($spclSRV{$rdata})) {
			    $spclSRV{$rdata} .= ", $spcl_name" unless $spclSRV{$rdata} =~ /$spcl_name/;
			} else {
			    $spclSRV{$rdata} = "$spcl_name";
			    #
			    # Since the RDATA field must properly point
			    # to a canonical domain name, enforce a
			    # stricter level of checking than was done
			    # on the NS record's owner field.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid SRV Target field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    }
		} elsif ($rrtype eq 'NSAP') {
		    #
		    # Make sure that the RDATA field meets the
		    # following syntax requirements:
		    #
		    #   * the hexadecimal string begins with "0x" (RFC-1706)
		    #   * optional readability separator is "."
		    #   * hex digits must occur in unseparated pairs
		    #   * at least one pair of hex digits is present
		    #
		    # NOTE: BIND 8.X additionally allows "+" and "/" as
		    #       optional readability separators but BIND 9.X
		    #       does not.  We'll use the BIND 9.X standard.
		    #
		    if ($rdata =~ /^\S+$/) {
			unless ($rdata =~ /^0x/i) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}RDATA field must begin with `0x' (RFC-1706)";
			    $Load_Status = 3;
			}
			$rdata =~ s/^0x([.]+)?//i;
			unless ($rdata) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}Missing hexadecimal digits in RDATA";
			    $Load_Status = 3;
			} else {
			    $rdata = uc($rdata);
			}
			while (length($rdata)) {
			    ($tmp, $rdata) = split(//, $rdata, 2);
			    next if $tmp eq ".";
			    unless ($tmp =~ /\d|[A-F]/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid character found in RDATA";
				$Load_Status = 3;
				last;
			    }
			    unless ($rdata && $rdata =~ /^(\d|[A-F])/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Unpaired hexadecimal digit in RDATA";
				$Load_Status = 3;
				last;
			    } else {
				$rdata =~ s/^.//;
			    }
			}
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RDATA field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'AFSDB') {
		    ($preference, $tmp) = split(' ', $rdata, 2);
		    $rdata = (defined($tmp)) ? $tmp : "";
		    unless ($preference =~ /^\d+$/ && $preference <= 65535) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid AFSDB Subtype value";
			$Load_Status = 3;
		    }
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($spclAFSDB{$rdata})) {
			    $spclAFSDB{$rdata} .= ", $spcl_name" unless $spclAFSDB{$rdata} =~ /$spcl_name/;
			} else {
			    $spclAFSDB{$rdata} = "$spcl_name";
			    #
			    # For RRtypes which have stricter name checking,
			    # make sure the check is contextually complete
			    # by using the FQDN.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid AFSDB Hostname field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    } elsif ($rdata) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid AFSDB Hostname field";
			$Load_Status = 3;
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Missing AFSDB Hostname field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'RT') {
		    ($preference, $tmp) = split(' ', $rdata, 2);
		    $rdata = (defined($tmp)) ? $tmp : "";
		    unless ($preference =~ /^\d+$/ && $preference <= 65535) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RT Preference value";
			$Load_Status = 3;
		    }
		    if ($rdata =~ /^\S+$/) {
			$rdata = $origin if $rdata eq '@';
			$rdata =~ s/$domain_pattern\.$//;
			if (exists($spclRT{$rdata})) {
			    $spclRT{$rdata} .= ", $spcl_name" unless $spclRT{$rdata} =~ /$spcl_name/;
			    if ($owner eq $zone) {
				if (exists($Apex_route_RRs{RT})
				    && exists($Apex_route_RRs{RT}{$rdata})) {
				    $n = ($message) ? ".\n" : "";
				    $message .= "${n}Redundant RT hostname";
				    $Load_Status = 1;
				    $Apex_route_RRs{RT}{$rdata} .= " $preference";
				} else {
				    $Apex_route_RRs{RT}{$rdata} = $preference;
				}
			    }
			} else {
			    $spclRT{$rdata} = "$spcl_name";
			    if ($owner eq $zone) {
				$Apex_route_RRs{RT}{$rdata} = $preference;
			    }
			    #
			    # For RRtypes which have stricter name checking,
			    # make sure the check is contextually complete
			    # by using the FQDN.
			    #
			    $tmp = $rdata;
			    $tmp .= $zone_suffix unless $tmp =~ /(^|[^\\])\.$/;
			    $tmp =~ s/\.$//;
			    $error = ($tmp) ? &CHECK_NAME($tmp, 'A') : 0;
			    if ($error) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid RT Intermediate-Host field";
				$Load_Status = $error if $error > $Load_Status;
			    }
			}
		    } elsif ($rdata) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Invalid RT Intermediate field";
			$Load_Status = 3;
		    } else {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Missing RT Intermediate field";
			$Load_Status = 3;
		    }
		} elsif ($rrtype eq 'SOA') {
		    unless ($verify_mode) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Warning: SOA RR is invalid in the `spcl' file context";
			$Load_Status = 3;
		    } else {
			#
			# "$SOA_count" is used to detect the duplicate SOA RR
			# that appears at the end of a successful zone transfer.
			# It's also used to prevent the duplicate record from
			# being reprocessed.
			#
			$SOA_count++;
			if ($SOA_count > 1) {
			    next unless $SOA_count != 2 || $message;
			    if ($SOA_count > 2) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Warning: found unexpected SOA record";
			    }
			} else {
			    ($RespHost, $RespUser, $Serial, $Refresh, $Retry, $Expire, $Ttl) = split(' ', $rdata, 7);
			    $RespHost = $origin if $RespHost eq '@';
			    $RespUser = $origin if $RespUser eq '@';
			    #
			    # Do not append the origin if the MNAME/RNAME
			    # field exists as, or ends with, an unescaped ".".
			    #
			    $RespHost .= $zone_suffix unless $RespHost =~ /(^|[^\\])\.$/;
			    $RespUser .= $zone_suffix unless $RespUser =~ /(^|[^\\])\.$/;
			    #
			    # Although a successful zone transfer should return
			    # a valid SOA record, there's a distinct possibility
			    # that it could have bad data (particularly in the
			    # RNAME field) if obtained from a non-BIND server.
			    #
			    if ($owner ne $zone) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Warning: owner name of SOA RR must match zone name";
			    } elsif ($owner !~ /\.$/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Warning: owner name of SOA RR must be absolute";
			    }
			    if ($RespHost ne ".") {
				($tmp = $RespHost) =~ s/\.$//;
				if ($tmp && &CHECK_NAME($tmp, 'A')) {
				    $n = ($message) ? ".\n" : "";
				    $message .= "${n}Invalid MNAME field in SOA RR";
				}
			    }
			    if ($RespUser ne ".") {
				$tmp = $RespUser;
				$tmp =~ s/(\\[.]|[^.])*\.//; # strip first label
				$tmp =~ s/\.$//;	     # strip last dot
				if (!$tmp || &CHECK_NAME($tmp, 'A')) {
				    $n = ($message) ? ".\n" : "";
				    $message .= "${n}Invalid RNAME field in SOA RR";
				}
			    }
			    unless ($Serial =~ /^\d+$/) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid Serial number";
			    }
			    unless ($Refresh =~ /^(\d+|(\d+[wdhms])+)$/i) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid Refresh interval";
				$valid_SOA_timers = 0;
			    }
			    unless ($Retry =~ /^(\d+|(\d+[wdhms])+)$/i) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid Retry interval";
				$valid_SOA_timers = 0;
			    }
			    unless ($Expire =~ /^(\d+|(\d+[wdhms])+)$/i) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid Expiry period";
				$valid_SOA_timers = 0;
			    }
			    unless ($Ttl =~ /^(\d+|(\d+[wdhms])+)$/i) {
				$n = ($message) ? ".\n" : "";
				$message .= "${n}Invalid default TTL/Negative Cache period";
				$valid_SOA_timers = 0;
			    }
			}
		    }
		}
		if ($owner =~ /^\*(\...*)?$zone_pattern$/ &&
		    $rrtype !~ /^(SOA|NS)$/ && $audit) {
		    #
		    # Keep track of wildcarded owner names and their
		    # RR types so that nothing is missed when the
		    # AUDIT_RRs subroutine is called.
		    #
		    ($tmp = $owner) =~ s/^\*\.//;
		    if (exists($Wildcards{$tmp})) {
			$Wildcards{$tmp} .= "$rrtype " unless $Wildcards{$tmp} =~ / $rrtype /;
		    } else {
			$Wildcards{$tmp} = " $rrtype ";
		    }
		}
	    } else {
		$message = "Warning: data outside zone `$zone' ignored";
	    }
	} elsif (/^\$INCLUDE($|\s+(\S+)?\s*(\S+)?\s*(\S+)?)/) {
	    $include_file = $2;
	    $new_origin = lc($3);
	    $data = $4;
	    unless ($include_file) {
		$message = "Unable to process \$INCLUDE - filename missing";
		$Load_Status = 3;
	    } else {
		if ($new_origin) {
		    $new_origin = $origin if $new_origin eq "@";
		    $new_origin .= $zone_suffix unless $new_origin =~ /(^|[^\\])\.$/;
		    ($tmp = $new_origin) =~ s/\.$//;
		    $error = ($tmp) ? &CHECK_NAME($tmp, 'NS') : 0;
		    if ($error) {
			$n = ($message) ? ".\n" : "";
			$message .= "${n}Domain name in origin argument is invalid";
			$Load_Status = $error if $error > $Load_Status;
		    }
		} else {
		    $new_origin = $origin;
		}
		if ($data) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Found uncommented text in comment field";
		}
		# Stop what we're doing with the current file and make a
		# recursive call to process the specified $INCLUDE file.
		# If the $INCLUDE directive also specified an origin
		# argument, it will be passed along as well.
		# Upon return, we'll automatically revert back to the
		# origin and current owner name that was in effect before
		# the subroutine call.  Restoration of the current owner
		# name is not specifically mentioned in RFC-1035 but is
		# implemented as a feature/deviation in BIND 8 and BIND 9.
		#
		$data = $_;		# $_ is global in scope - save it.
		$warning_status = &READ_RRs($include_file, $zone, $new_origin,
					    $owner, $warning_status);
		$_ = $data;
		if ($warning_status == 2) {
		    #
		    # The $INCLUDE filename could not be opened.  An error
		    # message has already been output but without the
		    # terminating newline character.
		    #
		    $message = "\n$message";
		    $warning_status = $newline_printed = 1;
		}
	    }
	} elsif (/^\$ORIGIN($|\s+(\S+)?\s*(\S+)?)/) {
	    $new_origin = lc($2);
	    $data = $3;
	    unless ($new_origin) {
		$message = "Missing argument from \$ORIGIN directive";
		$Load_Status = 3;
	    } else {
		if ($new_origin eq '..' && $verify_mode) {
		    #
		    # As a hack, get rid of the redundant trailing dot.
		    # DiG version 8.2 erroneously generates this $ORIGIN
		    # directive when it transfers the root zone.
		    #
		    $new_origin = ".";
		}
		$origin = $new_origin unless $new_origin eq "@";
		$origin .= $zone_suffix unless $origin =~ /(^|[^\\])\.$/;
		$zone_suffix = ($origin eq '.') ? "." : ".$origin";
		($tmp = $origin) =~ s/\.$//;
		$error = ($tmp) ? &CHECK_NAME($tmp, 'NS') : 0;
		if ($error) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Domain name is invalid";
		    $Load_Status = $error if $error > $Load_Status;
		}
		if ($data) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Found uncommented text in comment field";
		    $Load_Status = 3;
		}
	    }
	} elsif (/^\$TTL($|\s+(\S+)?\s*(\S+)?)/) {
	    $tmp = lc($2);
	    $data = $3;
	    unless ($tmp) {
		$message = "Missing argument from \$TTL directive";
	    } else {
		if ($tmp =~ /^(\d+|(\d+[wdhms])+)$/) {
		    $default_ttl = $tmp;
		} else {
		    $message = "Invalid TTL value - ignored";
		    $Load_Status = 3;
		}
		if ($data) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Found uncommented text in comment field";
		    $Load_Status = 3;
		}
	    }
	} elsif (/^\$GENERATE($|\s+(\S+)?\s*(\S+)?\s*(\S+)?\s*(\S+)?\s*(\S+)?)/) {
	    #
	    # This directive causes name server running BIND version 8.2
	    # or later to generate a set of resource records that differ
	    # from each other by one or more iterator values.
	    #
	    $range = $2;
	    $lhs = $3;
	    $rrtype = uc($4);
	    $rdata = $5;
	    $data = $6;
	    $gen_count++;
	    unless ($range) {
		$message = "Missing argument list from \$GENERATE directive";
		$Load_Status = 3;
	    } else {
		unless ($range =~ /(\d+)-(\d+)(.*)/) {
		    $message = "Invalid range argument";
		    $Load_Status = 3;
		} else {
		    $start = $1;
		    $stop = $2;
		    $step = $3;
		    if ($stop < $start) {
			$message = "Invalid range (stop < start)";
			$Load_Status = 3;
		    }
		    if ($step) {
			unless ($step =~ /^\/(\d+)$/) {
			    $n = ($message) ? ".\n" : "";
			    $message .= "${n}Invalid range argument";
			    $Load_Status = 3;
			} else {
			    $step = $1;
			}
		    } else {
			$step = 1;
		    }
		}
		unless ($lhs) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Missing LHS argument";
		    $Load_Status = 3;
		}
		unless ($rrtype) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Missing RRtype argument";
		    $Load_Status = 3;
		} elsif ($rrtype !~ /^(A|AAAA|CNAME|DNAME|NS|PTR)$/) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Unsupported RRtype argument";
		    $Load_Status = 3;
		}
		unless ($rdata) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Missing RHS argument";
		    $Load_Status = 3;
		}
		if ($data) {
		    $n = ($message) ? ".\n" : "";
		    $message .= "${n}Found uncommented text in comment field";
		}
	    }
	    unless ($message) {
		#
		# Now that the basic syntax of the directive has been
		# checked, it's time to register the RRs that BIND
		# will generate into the appropriate global hash(es).
		# This will be done as follows:
		#
		#   1. Loop through the range specification and write
		#      the resulting RRs to a temporary file.
		#   2. Take the same action as though the temporary
		#      file appeared in an $INCLUDE directive - make
		#      a recursive call to READ_RRs to register the
		#      RRs into the appropriate data structure(s).
		#   3. Remove the temporary file upon returning from
		#      the recursive subroutine call and continue
		#      processing the `spcl' file.
		#
		$gen_file = "$debug_DIR/GENERATE_#$gen_count";
		unless (&OPEN(*GEN, "> $gen_file")) {
		    $message = "Couldn't open temporary working file: $!";
		} else {
		    $gen_owner = &GEN_NAME($lhs);
		    $message = "GEN_NAME: invalid LHS template" unless $gen_owner;
		    $gen_rdata = &GEN_NAME($rdata);
		    unless ($gen_rdata) {
			unless ($gen_owner) {
			    $message = "GEN_NAME: invalid LHS and RHS templates";
			} else {
			    $message = "GEN_NAME: invalid RHS template";
			}
		    }
		    unless ($message) {
			$data = ($rrtype eq 'PTR') ? 16 : 24;
			for ($i = $start; $i <= $stop; $i += $step) {
			    #
			    # The eval() function will substitute the current
			    # value of "$i" and concatenate any fixed strings
			    # in "$gen_owner" and/or "$gen_rdata" to generate
			    # RRs with the corresponding owner and RDATA fields.
			    # 
			    printf(GEN "%s\t%s\t%s\n", &TAB(eval($gen_owner), $data), $rrtype, eval($gen_rdata));
			}
		    }
		    &CLOSE(*GEN);
		    unless ($message) {
			#
			# Make the recursive call that will register the
			# generated RRs into the relevant data structure(s).
			#
			$data = $_;
			$warning_status = &READ_RRs($gen_file, $zone, $origin,
						    $owner, $warning_status);
			$_ = $data;
			if ($warning_status == 2) {
			    #
			    # The temporary working file was not able to
			    # be opened.  An error message has already been
			    # output but without the terminating newline
			    # character.
			    #
			    $message = "\n$message";
			    $warning_status = $newline_printed = 1;
			}
		    }
		    unlink($gen_file) unless $debug;
		}
	    }
	} elsif (/^\$/) {
	    $message = "Unknown directive";
	    $Load_Status = 3;
	} else {
	    $message = "Invalid DNS record [unknown RR type and/or missing/extra field(s)] - ignored.\n";
	    $Load_Status = 3;
	}
	if ($message && $verbose) {
	    if ($verify_mode) {
		$message .= "." unless $message =~ /\n$/;
		$message =~ s/\n$//;		# prevents double-spaced lines
	    } else {
		$n = ($message =~ /\n$/) ? "" : "; ";
		if ($spcl_name eq "-T option") {
		    $message .= "${n}-T option";
		} else {
		    $message .= "${n}file $spcl_name, line $line_num";
		}
	    }
	    print STDERR "\n" unless $newline_printed;
	    $original_line =~ s/\n$//;		# prevents double-spaced lines
	    if ($rr && $original_line =~ /^(\S+)?\s+(.+)/s) {
		#
		# Perform some cosmetic data manipulations to
		# ensure the clarity of the diagnostic output.
		#
		$data = $1;
		$tmp = $2;
		if (defined($data)) {
		    $data = ($owner eq $origin) ? '@' : $owner;
		    $data =~ s/$zone_pattern$//;
		} else {
		    $data = "";
		}
		$n = length($data);
		$n = ($n < 24) ? 24 : $n;
		printf STDERR "%s\n%s%s\n", $message, &TAB("> $data", $n), $tmp;
	    } else {
		print STDERR "$message\n> $original_line\n";
	    }
	    $message = $n = "";
	    $warning_status = $newline_printed = 1;
	}
    }
    &CLOSE(*SPCL);
    if ($open_quote || $open_paren_count) {
	if ($verbose) {
	    $char = ($open_quote) ? "quotes" : "parentheses";
	    print STDERR "Unable to process file `$spcl_file'\ndue to unbalanced $char.  The syntax problem begins at line $split_line_num.\n";
	    $warning_status = $newline_printed = 1;
	}
	$Load_Status = 4;
    }
    return $warning_status;
}


#
# Subroutine to determine if a domain name is part of
# a child domain or matches a wildcard owner name.
#
# Return value:
#   ($subzone, $wildcard, $wildcard_rrtypes)
#
#   The null string is returned for any list element which
#   does not have a matching value.
#
sub MATCH_DOMAIN {
    my ($domain_name) = @_;
    my ($subzone, $uq_domain_name, $wildcard);
    my ($wildcard_matching_OK, $wildcard_rrtypes);

    # There are two basic approaches for seeing if a domain
    # name matches a child zone or wildcard:
    #
    #  1. Fetch child zones/wildcards from their respective
    #     hashes until a match is found or there are no more
    #     hash indices to compare.  This is a classic example
    #     of an N**2 search algorithm which becomes very slow
    #     for a zone with many child zones and/or wildcards.
    #
    #  2. [a] See if the current domain name matches that
    #         of a child zone/wildcard.
    #     [b] If no match is found, strip the first label
    #         from the current domain name and retry step [a]
    #         until either a match is found or the remaining
    #         label(s) match the current zone being audited.
    #
    # The cumulative search time for the second method has a linear
    # relationship to the number of child zones/wildcards and, thus,
    # is implemented here.
    #
    $subzone = $wildcard = $wildcard_rrtypes = "";
    $wildcard_matching_OK = 1;
    #
    # NOTE: The "$domain_name" parameter as well as the global variables
    #       "$Domain" and "$Domainpattern" have already been lower-cased
    #       by &AUDIT_RRs(), the sole caller of this subroutine.
    #
    while ($domain_name && ".$domain_name" =~ /$Domainpattern\.$/) {
	($uq_domain_name = $domain_name) =~ s/$Domainpattern\.$//;
	#
	# The following test will be made for each label of the passed
	# domain name until either a subzone is matched or the current
	# zone apex is reached.  Even if a wildcard is ostensibly matched
	# in the subsequent block, we need to keep testing for a subzone
	# since delegation cancels wildcard matching.
	#
        if ($domain_name ne "$Domain." && exists($NSowners{$uq_domain_name})) {
	    $subzone = $domain_name;
	    #
	    # Per RFC-1034, the wildcard matching algorithm does not apply
	    # across zone boundaries, i.e., delegation cancels the wildcard
	    # defaults.  Make sure to nullify any such previous match.
	    #
	    $wildcard = $wildcard_rrtypes = "" if $wildcard;
	    last;
	}
	if ($wildcard_matching_OK) {
	    #
	    # The following wildcard tests will be made for each label
	    # of the passed domain name until one of the following
	    # conditions is met:
	    #
	    #   1. A DNS node (either empty or owning one or more RRs)
	    #      exists at or between the passed domain name and an
	    #      otherwise-matching wildcard.
	    #
	    #   2. A wildcard is matched.  This keeps the longest match in
	    #      case another wildcard with fewer labels would otherwise
	    #      match as well.
	    #
	    #   3. A subzone is matched.  This cancels any previous wildcard
	    #      match.
	    #
	    #   4. The label(s) representing the current zone apex is/are
	    #      reached.
	    #
	    if ($uq_domain_name ne $domain_name
		&& exists($RRowners{$uq_domain_name})) {
		#
		# If the passed domain name explicitly matches a DNS node
		# (empty or having any resource record) or a DNS node is
		# known to exist in the DNS hierarchy between the passed
		# domain name and the nearest intra-zone wildcard, a
		# wildcard match must not occur according to RFC-1034.
		# For example, given the following RRs:
		#
		#   foo.example.com.      A      192.168.1.1
		#   foo.moo.example.com.  A      192.168.2.2
		#   *.example.com.        MX     10 foo.example.com.
		#   zoo.example.com.      CNAME  zoo.foo.example.com.
		#   goo.example.com.      CNAME  fee.fie.example.com.
		#
		# the resolution of `zoo.foo.example.com' would be
		# NXDOMAIN since `foo.example.com' exists as a node
		# in the DNS namespace (it also happens to be an owner
		# of an RR) and does not have a wildcard.  NXDOMAIN
		# would also be returned for `zoo.moo.example.com'
		# since `moo.example.com' exists as a DNS node even
		# though that domain name does not own any records.
		# The domain `fee.fie.example.com', on the other hand,
		# does match the wildcard since there is no explicit
		# match for that owner name nor for `fie.example.com'.
		# However, if the following record were added:
		#
		#   *.foo.example.com.    A      192.168.3.3
		#
		# then `zoo.foo.example.com' would match this more
		# explicit wildcard.
		#
		$wildcard_matching_OK = 0 if !exists($Wildcards{$domain_name});
	    }
	    if ($wildcard_matching_OK && exists($Wildcards{$domain_name})) {
		#
		# The longest-matching wildcard (may be multi-label) has
		# been found.  Once stored into the "$wildcard" variable,
		# further wildcard matching attempts are cancelled.
		# We will not exit the label-trimming loop, however,
		# because the RFC-1034 wildcard algorithm requires us
		# to continue searching for a matching subzone which
		# will cancel this wildcard match.
		#
		$wildcard = "*.$domain_name";
		$wildcard_rrtypes = $Wildcards{$domain_name};
		$wildcard_matching_OK = 0;
	    }
	}
	# Find the leading label, i.e., the characters up to
	# and including the first unescaped ".", and remove
	# it from the domain name prior to the next attempt
	# at matching a child domain/wildcard.
	#
	$domain_name =~ s/(\\[.]|[^.])*\.//;
    }
    return ($subzone, $wildcard, $wildcard_rrtypes);
}


#
# Perform some final error checking that is only possible after
# all of this zone's data has been accounted for.
# Checks are made for the following conditions:
#   * NS, MX, PTR, SRV, RT, and AFSDB records that point to CNAMEs or
#     domain names with no Address records or to nonexistent domains.
#   * RP records with TXTDNAME fields that reference nonexistent
#     TXT records.
#   * CNAME records that point to nonexistent domain names, i.e.,
#     "dangling" CNAMEs.
#   * Zones with only one listed name server (violates RFC-1034),
#     NS RRsets with inconsistent TTL values (violates RFC-2181),
#     NS RRs with missing glue records, and non-glue records at
#     or below a zone cut.
# This subroutine assumes that the `DiG' program is available for
# resolving domain names that are external to the zone being processed.
#
# Having these checks performed by `h2n' after it builds the zone
# data files is useful because such misconfigurations are not reported
# in the syslog when the zone is initially loaded by `named'.  To do so
# would require `named' to make two passes though the data, an impractical
# task to add to its overhead.  Even though `named' eventually logs these
# domain names when they are accessed, it's also impractical to expect
# busy hostmasters to be constantly scanning the syslog(s) for such events.
#
# NOTE: Since this subroutine could be called for every run of `h2n'
#       that processes a host file, careful consideration should be made
#       before adding any more data verification tasks.  Additional data
#       checks which are time-consuming and/or esoteric may be better
#       placed in the "CHECK_ZONE" subroutine and called with the
#       -V option instead.
#
# Return values:
#   0 = no warnings
#   1 = warnings
#
sub AUDIT_RRs {
    my ($warning_status) = @_;
    my ($DiG_batch, $additional, $answer, $authority, $buffer, $chain_length);
    my ($cname, $cname_loop, $cname_n, $debug_file, $domain, $flags);
    my ($first_answer, $fq_host, $host, $i, $k, $last_zone, $location);
    my ($match, $max_chain_length, $n, $nextbatch, $nextbatch_is_open);
    my ($ns_count, $query_options, $query_section, $result, $rrtype);
    my ($saved_cname, $status, $subzone, $subzones_exist, $t, $tmp, $ttl);
    my ($warning, $wildcard, $wildcards_exist, $zone);
    my (%baseCNAME, %chainCNAME, %extAFSDB, %extCNAME, %extMX, %extPTR, %extRP);
    my (%extRT, %extSRV, %glue_RRs, %non_glue_RRs);
    my (@NSrfc1034, @NSrfc2181, @delete_list, @temp);

    $n = ($warning_status) ? "" : "\n";	# controls output of cosmetic newlines
    if ($verify_mode) {			# other cosmetic considerations
	$t = 40;
	$location = "(SOA MNAME)";
    } else {
	$t = 32;
	$location = "-h option";
    }
    $Domain = lc($Domain);		    # in case the -P option is in effect
    $Domainpattern = lc($Domainpattern);    # ditto
    $domain = ($Domain) ? "$Domain." : "";  # accommodate the root zone
    #
    # Add the master name server in the `-h' option or, if in verify mode,
    # the name server from the SOA MNAME field, to the list of any name
    # servers found in the READ_RRs subroutine.
    #
    unless ($RespHost =~ /^\.?$/) {
	($host = lc($RespHost)) =~ s/$Domainpattern\.$//;
	if (exists($NSlist{$host})) {
	    $NSlist{$host} = "$location, $NSlist{$host}";
	} else {
	    $NSlist{$host} = $location;
	}
    }
    unless ($verify_mode) {
	#
	# Add the name servers from the `-s' and `-S' options to the data
	# set of other name servers that may have found when reading a
	# `spcl' file.  Similarly, mailhosts from any `-m' options will
	# be added to any `spcl' mailhosts.
	#
	foreach $host (@FullServers) {
	    ($host = lc($host)) =~ s/$Domainpattern\.$//;
	    if (exists($NSlist{$host})) {
		$location = $NSlist{$host};
		if ($location =~ /-h option/) {
		    $location =~ s/-h option/-h,-s options/;
		} elsif ($location !~ /-s option/) {
		    $location = "-s option, $location";
		}
		$NSlist{$host} = $location;
	    } else {
		$NSlist{$host} = "-s option";
	    }
	    # Register the NS RRs of this domain in the same manner
	    # as any child domains that were found in a `spcl' file.
	    #
	    $NSowners{"$Domain."} .= " " . &SECONDS($DefTtl) . " $host";
	}
	foreach $tmp (keys %PartialServers) {
	    @temp = split(' ', $PartialServers{$tmp});
	    foreach $host (@temp) {
		($host = lc($host)) =~ s/$Domainpattern\.$//;
		if (exists($NSlist{$host})) {
		    $location = $NSlist{$host};
		    if ($location =~ /-[hs] option/) {
			$location =~ s/([^ ]+) options?/$1,-S options/;
		    } elsif ($location !~ /-S option/) {
			$location = "-S option, $location";
		    }
		    $NSlist{$host} = $location;
		} else {
		    $NSlist{$host} = "-S option";
		}
		# Register the NS RRs of the forward-mapping domain.
		#
		$NSowners{"$Domain."} .= " " . &SECONDS($DefTtl) . " $host" if $tmp eq $Domainfile;
	    }
	}

	if ($do_mx || $do_zone_apex_mx) {
	    #
	    # Combine the `-m' and `spcl' MX data structures into one.
	    # The %MXlist hash will track each mailhost and where it was found.
	    # NOTE: In-zone hostnames in the @MX array are already stored
	    #       as UQDNs.
	    #
	    foreach $buffer (@MX) {
		($tmp, $host) = split(' ', $buffer);
		$host = ($host eq '@') ? "$Domain." : lc($host);
		if (exists($MXlist{$host})) {
		    $location = $MXlist{$host};
		    if ($location =~ /^-T option/) {
			$location =~ s/-T option/-T,-m options/;
		    } elsif ($location !~ /-m option/) {
			$location = "-m option, $location";
		    }
		    $MXlist{$host} = $location;
		} else {
		    $MXlist{$host} = "-m option";
		}
	    }
	}
    }

    # Undefine data structures that are no longer needed so that
    # the "%ext*" hashes have the maximum amount of room to grow
    # as each RR type is audited.
    #
    undef @FullServers;
    undef %PartialServers;
    undef @MX;
    undef @temp;

    $subzones_exist = keys(%NSowners);
    $wildcards_exist = keys(%Wildcards);
    scalar(keys(%NSlist));			# Reset the iterator!
    while (($host, $location) = each %NSlist) {
	#
	# Try to find an explicit match in the local domain.
	#
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $NSlist{$host} = "[CNAME record]|$NSlist{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $NSlist{$host} = "[no RRs exist]|$NSlist{$host}";
		} else {
		    $match = $warning = 1;
		    $NSlist{$host} = "[no addr. RR ]|$NSlist{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		#
		# If no explicit match was found, try matching an existing
		# wildcard.  In any case, however, see if the domain name of
		# this NS host belongs to a delegated subdomain.  If so, the
		# `DiG' program will be called upon to make sure that the
		# domain name can be resolved.
		#
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extNS{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$NSlist{$host} = "[$warning]|$NSlist{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$NSlist{$host} = "[no such name]|$NSlist{$host}";
	    }
	} else {
	    #
	    # Save the external NS host for `DiG' to look up later.
	    #
	    $extNS{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    # Now that we're no longer iterating over %NSlist, remove
    # the hash keys that were marked for deletion.
    #
    foreach $host (@delete_list) {
	delete($NSlist{$host});
    }
	
    @delete_list = ();
    scalar(keys(%MXlist));
    while (($host, $location) = each %MXlist) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $MXlist{$host} = "[CNAME record]|$MXlist{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $MXlist{$host} = "[no RRs exist]|$MXlist{$host}";
		} else {
		    $match = $warning = 1;
		    $MXlist{$host} = "[no addr. RR ]|$MXlist{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extMX{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$MXlist{$host} = "[$warning]|$MXlist{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$MXlist{$host} = "[no such name]|$MXlist{$host}";
	    }
	} else {
	    $extMX{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($MXlist{$host});
    }

    # Attempt to reconcile the Target domain name of any SRV RRs
    # that were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclSRV));
    while (($host, $location) = each %spclSRV) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $spclSRV{$host} = "[CNAME record]|$spclSRV{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $spclSRV{$host} = "[no RRs exist]|$spclSRV{$host}";
		} else {
		    $match = $warning = 1;
		    $spclSRV{$host} = "[no addr. RR ]|$spclSRV{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extSRV{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$spclSRV{$host} = "[$warning]|$spclSRV{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclSRV{$host} = "[no such name]|$spclSRV{$host}";
	    }
	} else {
	    $extSRV{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclSRV{$host});
    }

    # Attempt to reconcile the RDATA field of any PTR RRs that
    # were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclPTR));
    while (($host, $location) = each %spclPTR) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6|NSAP) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $spclPTR{$host} = "[CNAME record]|$spclPTR{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $spclPTR{$host} = "[no RRs exist]|$spclPTR{$host}";
		} else {
		    $match = $warning = 1;
		    $spclPTR{$host} = "[no addr. RR ]|$spclPTR{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extPTR{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6|NSAP) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$spclPTR{$host} = "[$warning]|$spclPTR{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclPTR{$host} = "[no such name]|$spclPTR{$host}";
	    }
	} else {
	    $extPTR{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclPTR{$host});
    }

    # Attempt to reconcile the HOSTNAME field of any AFSDB RRs that
    # were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclAFSDB));
    while (($host, $location) = each %spclAFSDB) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $spclAFSDB{$host} = "[CNAME record]|$spclAFSDB{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $spclAFSDB{$host} = "[no RRs exist]|$spclAFSDB{$host}";
		} else {
		    $match = $warning = 1;
		    $spclAFSDB{$host} = "[no addr. RR ]|$spclAFSDB{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extAFSDB{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$spclAFSDB{$host} = "[$warning]|$spclAFSDB{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclAFSDB{$host} = "[no such name]|$spclAFSDB{$host}";
	    }
	} else {
	    $extAFSDB{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclAFSDB{$host});
    }

    # Attempt to reconcile the INTERMEDIATE-HOST field of any RT RRs
    # that were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclRT));
    while (($host, $location) = each %spclRT) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / (A|AAAA|A6|ISDN|X25) /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $spclRT{$host} = "[CNAME record]|$spclRT{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $spclRT{$host} = "[no RRs exist]|$spclRT{$host}";
		} else {
		    $match = $warning = 1;
		    $spclRT{$host} = "[no addr. RR ]|$spclRT{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extRT{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / (A|AAAA|A6|ISDN|X25) /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*) non-A RR";
			}
			$spclRT{$host} = "[$warning]|$spclRT{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclRT{$host} = "[no such name]|$spclRT{$host}";
	    }
	} else {
	    $extRT{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclRT{$host});
    }

    # Attempt to reconcile the TXTDNAME field of any RP RRs that
    # were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclRP));
    while (($host, $location) = each %spclRP) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($RRowners{$host})) {
		$rrtype = $RRowners{$host};
		if ($rrtype =~ / TXT /) {
		    $match = 1;
		} elsif ($rrtype =~ / CNAME /) {
		    $match = $warning = 1;
		    $spclRP{$host} = "[CNAME record]|$spclRP{$host}";
		} elsif ($rrtype eq " ") {
		    $match = $warning = 1;
		    $spclRP{$host} = "[no RRs exist]|$spclRP{$host}";
		} else {
		    $match = $warning = 1;
		    $spclRP{$host} = "[ no TXT RR  ]|$spclRP{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $warning = 0;
		    $extRP{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		    unless ($rrtype =~ / TXT /) {
			if ($rrtype =~ / CNAME /) {
			    $warning = "(*) CNAME RR";
			} elsif ($rrtype =~ / MX /) {
			    $warning = " (*) MX RR  ";
			} else {
			    $warning = "(*)nonTXT RR";
			}
			$spclRP{$host} = "[$warning]|$spclRP{$host}";
		    }
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclRP{$host} = "[no such name]|$spclRP{$host}";
	    }
	} else {
	    $extRP{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclRP{$host});
    }

    # Attempt to reconcile the RDATA field of any CNAMEs that
    # were found while scanning a `spcl' or zone data file.
    #
    @delete_list = ();
    scalar(keys(%spclCNAME));
    while (($host, $location) = each %spclCNAME) {
	if ($host !~ /[^\\]\.$/ || $host eq "$Domain.") {
	    $match = $warning = 0;
	    if (exists($Hosts{$host})) {
		$match = 1;
	    } elsif (exists($RRowners{$host})) {
		$match = 1;
		if ($RRowners{$host} eq " ") {
		    $warning = 1;
		    $spclCNAME{$host} = "[no RRs exist]|$spclCNAME{$host}";
		}
	    }
	    if ($subzones_exist || (!$match && $wildcards_exist)) {
		$fq_host = ($host eq "$Domain.") ? $host : "$host.$domain";
		($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN($fq_host);
		if ($subzone) {
		    $match = 1;
		    $extCNAME{$fq_host} = $location;
		} elsif (!$match && $wildcard) {
		    $match = 1;
		}
	    }
	    if ($match) {
		push(@delete_list, $host) unless $warning;
	    } else {
		$spclCNAME{$host} = "[no such name]|$spclCNAME{$host}";
	    }
	} else {
	    $extCNAME{$host} = $location;
	    push(@delete_list, $host);
	}
    }
    foreach $host (@delete_list) {
	delete($spclCNAME{$host});
    }

    $answer = keys(%extNS) + keys(%extMX) + keys(%extSRV) + keys(%extPTR) +
	      keys(%extAFSDB) + keys(%extRT) + keys(%extRP) + keys(%extCNAME);
    if ($answer) {
	#
	# Use `DiG' to to find out if the external domain names refer
	# to CNAMEs, lack Address records, or if they even exist.
	#
	$i = "00";
	$chain_length = $max_chain_length = 0;
	$DiG_batch = "$debug_DIR/h2n-dig.bat${i}_$data_fname";
	unless (open(*DIGBATCH, "> $DiG_batch")) {
	    print STDERR "Couldn't create batch file for `DiG': $!\n";
	    print STDERR "Unable to check external domain names.\n";
	    %extNS = %extMX = %extPTR = %extRP = %extCNAME = ();
	} else {
	    while (($host, $tmp) = each %extNS) {
		print DIGBATCH "$host A \%NS\n";
	    }
	    while (($host, $tmp) = each %extMX) {
		print DIGBATCH "$host A \%MX\n";
	    }
	    while (($host, $tmp) = each %extSRV) {
		print DIGBATCH "$host A \%SRV\n";
	    }
	    while (($host, $tmp) = each %extPTR) {
		print DIGBATCH "$host A \%PTR\n";
	    }
	    while (($host, $tmp) = each %extAFSDB) {
		print DIGBATCH "$host A \%AFSDB\n";
	    }
	    while (($host, $tmp) = each %extRT) {
		print DIGBATCH "$host A \%RT\n";
	    }
	    while (($host, $tmp) = each %extRP) {
		print DIGBATCH "$host TXT \%RP\n";
	    }
	    while (($host, $tmp) = each %extCNAME) {
		print DIGBATCH "$host ANY \%CNAME\n";
	    }
	    close(*DIGBATCH);

	    if ($answer == 1) {
		$tmp = "query for an out-of-zone domain";
	    } else {
		$tmp = "queries for out-of-zone domains";
	    }
	    print STDOUT "(processing $answer $tmp)\n";
	    $query_options  = " +noauthority +noadditional +nostats";
	    $query_options .= " +$DiG_timeout +$DiG_retries";
	    while (-s $DiG_batch) { 
		#
		# Call DiG to process the original batch file plus any
		# subsequent batch files that get generated as a result
		# of following chained CNAMEs.
		# 
		$debug_file = "$debug_DIR/h2n-dig.ans${i}_$data_fname";
		if ($debug) {
		    unless (open(*DEBUGOUT, "> $debug_file")) {
			print STDERR "Error opening `$debug_file': $!\n";
			print STDERR "Disabling the -debug option for the remainder of the program.\n";
			$debug = 0;
		    }
		}
		unless (open(*DIGOUT, "$DiG $query_options -f $DiG_batch 2>&1 |")) {
		    print STDERR "Error running the `$DiG' program: $!\n";
		    print STDERR "Unable to check external domain names.\n";
		    %extNS = %extMX = %extSRV = %extPTR = %extAFSDB = %extRT =
			     %extRP = %extCNAME = ();
		    close(*DEBUGOUT) if $debug;
		    $nextbatch = "";
		    last;
		}
		$i++;
		$chain_length++;
		$nextbatch = "$debug_DIR/h2n-dig.bat${i}_$data_fname";
		unless (open(*NEXTBATCH, "> $nextbatch")) {
		    $nextbatch_is_open = 0;
		} else {
		    $nextbatch_is_open = 1;
		}
		$rrtype = $saved_cname = "";
		while (<DIGOUT>) {
		    print DEBUGOUT $_ if $debug;
		    chop;
		    next if /^$/;
		    if (/^; <<>> DiG \d+\.\d+.* <<>>.* (\S+) (AN?Y?|TXT) %(NS|MX|SRV|PTR|AFSDB|RT|RP|CNAME)=?(\S+)?/) {
			$host = $1;
			$rrtype = $3;
			$cname = $4;
			if ($rrtype eq 'CNAME') {
			    #
			    # Deal with the various work-arounds that this
			    # program has to make in order to accommodate a
			    # small internal buffer size in DiG version 8.2
			    # and earlier whereby it overruns the buffer when
			    # reading a line from a batch file that exceeds
			    # 100 characters in length.
			    #
			    if ($chain_length == 1) {
				#
				# When the original batch file is created,
				# the starting CNAME ($cname) is identical
				# to the domain name to be queried ($host).
				# To save buffer space, the redundant field
				# is not written to the batch file.
				#
				$cname = $host;
			    } elsif ($host eq '<NO-OP>') {
				#
				# When subsequent batch files are created to
				# follow chained CNAMEs, it may be necessary
				# to split the batch file entry into two lines.
				# A dummy name is assigned to "$host" followed
				# by the real base CNAME in its expected field.
				#
				$saved_cname = $cname;
			    } elsif ($saved_cname) {
				#
				# This section is reached when the second line
				# of the split-line work-around is reached.
				# The "$host" variable contains the chained
				# CNAME to be queried while the "$cname"
				# variable has just been set to null.
				# Restore the value of the base CNAME from the
				# prior input line to complete the work-around.
				#
				$cname = $saved_cname;
				$saved_cname = "";
			    }
			}
			$status = $result = "";
			$query_section = $first_answer = 0;
			next;
		    } elsif (/^;.+(connection |no route|unreachable|...malformed|packet size error|Message too long)/i) {
			#
			# Check the "$rrtype" variable as a precaution against
			# accidentally overwriting the result of the last
			# successful lookup in case synchronization is lost.
			# This step is necessary in order to accommodate
			# versions 9.0.1 of DiG which fail to echo the command
			# line when encountering any type of connection failure.
			#
			next unless $rrtype;
			s/.+(timed out).*/ $1  /;
			s/.+(refused).*/con. $1/;
			s/.+(no route).+/  $1  /;
			s/.+(unreachable).+/$1 /;
			s/.+(...malformed|packet size error|Message too long).*/bad DNS msg./i;
			$result = $_;
			#
			# Instead of getting the next line, we'll fall through
			# to the section where a non-null "$result" variable
			# is processed and throw in the towel for this batch
			# file entry.  It's pointless and likely futile to
			# wait for the sanity check in the Query Section.
			#
		    } elsif (/^;.+HEADER.+opcode: QUERY, status: ([^,]+)/) {
			$status = $1;
			if ($status ne 'NOERROR') {
			    $status = " $status " if length($status) == 6;
			    $status .= " " if length($status) < 8;
			    $result = "  $status  ";
			    $status = "";
			}
			next;		# trust nothing until the Query Section
		    } elsif ($status && /^;.+flags: (.*); QUE.*, ANS[^:]*: (\d+), AUTH[^:]*: (\d+), ADDIT[^:]*: (\d+)/i) {
			$flags = $1;
			$answer = $2;
			$authority = $3;
			$additional = $4;
			if ($answer == 0) {
			    if ($rrtype =~ /^(NS|MX|SRV|PTR|AFSDB|RT)$/) {
				$result = "no A record ";
			    } elsif ($rrtype eq 'RP') {
				$result = " no TXT RR  ";
			    } else {
				#
				# Hmm.  The domain name must exist since
				# the status of the query is NOERROR and
				# yet there are no answers for ANY resource
				# records.  This can happen when querying
				# for an interior label of an existing
				# domain name, e.g.,
				#
				#  a.foo.example.com   exists as a RR in the
				#                      `example.com' zone
				#  foo.example.com     does *not* exist as a RR
				#
				# We would be here if an ANY query were made
				# for the domain name `foo.example.com' since
				# no RRs exist at that node in the DNS
				# namespace hierarchy.
				#
				$result = "no RRs exist";
			    }
			    $status = "";
			} else {
			    #
			    # Prepare for the Answer Section once we pass
			    # the sanity check in the Query Section.
			    #
			    $first_answer = 1;
			}
			next;		# trust nothing until the Query Section
		    } elsif (/^;; QUE.+:$/) {
			$query_section = 1;
			next;
		    }
		    next if !$rrtype || $host eq '<NO-OP>';
		    if ($query_section && /^;;?\s*(\S+)\s+/) {
			$tmp = lc($1);
			$query_section = 0;
			if ($tmp =~ /,$/) {
			    #
			    # DiG versions through 8.X match this pattern.
			    #
			    $tmp =~ s/,$/./;
			    $tmp = "." if $tmp eq "..";	# '.' zone special case
			}
			if ($tmp ne $host) {
			    #
			    # The domain name that was passed to DiG in the
			    # batch file and which appears on the echoed
			    # command line is *not* what DiG actually used 
			    # when making its query.  A line in the batch
			    # file that exceeded "$DiG_bufsize" in length
			    # is the likely source of DiG's buffer overrun.
			    # All we can do now is report what occurred and
			    # then resynchronize on the next line from the
			    # batch input file.
			    #
			    $result = "buffer error";
			    $status = "";
			    $first_answer = 0;
			    if ($chain_length > 1 && exists($baseCNAME{$host})) {
				#
				# Recover the "$cname" field that was most
				# likely trashed due to the buffer overrun.
				#
				$cname = $baseCNAME{$host};
			    }
			}
		    }
		    if ($chain_length > 1) {
			#
			# The backup copy of the base CNAME is no longer needed.
			# Remove the chained CNAME key for possible re-use.
			#
			delete($baseCNAME{$host});
		    }
		    if ($result) {
			if ($rrtype eq 'NS') {
			    $extNS{$host} = "[$result]|$extNS{$host}";
			} elsif ($rrtype eq 'MX') {
			    $extMX{$host} = "[$result]|$extMX{$host}";
			} elsif ($rrtype eq 'SRV') {
			    $extSRV{$host} = "[$result]|$extSRV{$host}";
			} elsif ($rrtype eq 'PTR') {
			    $extPTR{$host} = "[$result]|$extPTR{$host}";
			} elsif ($rrtype eq 'AFSDB') {
			    $extAFSDB{$host} = "[$result]|$extAFSDB{$host}";
			} elsif ($rrtype eq 'RT') {
			    $extRT{$host} = "[$result]|$extRT{$host}";
			} elsif ($rrtype eq 'RP') {
			    $extRP{$host} = "[$result]|$extRP{$host}";
			} else {
			    if ($chain_length == 1) {
				#
				# Despite the name of the control variable,
				# there's no CNAME chain here; a bigger
				# problem has caused us to bail out early.
				#
				if ($show_nxdomain_cnames
				    || $result !~ /(NXDOMAIN|no RRs exist)/) {
				    $extCNAME{$cname} = "[$result]|$extCNAME{$cname}";
				} else {
				    #
				    # We are not interested in reporting any
				    # dangling CNAMEs.
				    #
				    delete($extCNAME{$cname});
				}
			    } elsif (exists($extCNAME{$cname})) {
				#
				# The reason we tested for the existence of
				# "$cname" as a hash key is that we might
				# not have been able to recover the backup
				# copy after a buffer overrun was detected.
				#
				if ($show_chained_cnames
				    || $show_nxdomain_cnames
				    || $result !~ /(NXDOMAIN|no RRs exist)/) {
				    $extCNAME{$cname} =~ s/\[.+\]/[$result]/;
				    $max_chain_length = $chain_length;
				} else {
				    delete($extCNAME{$cname});
				}
			    }
			}
			# Setting "$rrtype" to null after detecting either a
			# buffer overrun or a non-NOERROR query status will
			# effectively ignore DiG's output until the next echoed
			# DiG command in the output stream.  This effectively
			# allows a resynchronization to take place and prevents
			# the accidental overwriting of valid data.
			#
			$rrtype = "" if $result eq "buffer error" || !$status;
			$result = "";
		    }
		    next unless $status;
		    if ($first_answer && /^[^;]/) {
			if (/\sCNAME\s+(\S+)$/) {
			    $cname_n = lc($1);
			    if ($rrtype eq 'NS') {
				$extNS{$host} = "[CNAME record]|$extNS{$host}";
			    } elsif ($rrtype eq 'MX') {
				$extMX{$host} = "[CNAME record]|$extMX{$host}";
			    } elsif ($rrtype eq 'SRV') {
				$extSRV{$host} = "[CNAME record]|$extSRV{$host}";
			    } elsif ($rrtype eq 'PTR') {
				$extPTR{$host} = "[CNAME record]|$extPTR{$host}";
			    } elsif ($rrtype eq 'AFSDB') {
				$extAFSDB{$host} = "[CNAME record]|$extAFSDB{$host}";
			    } elsif ($rrtype eq 'RT') {
				$extRT{$host} = "[CNAME record]|$extRT{$host}";
			    } elsif ($rrtype eq 'RP') {
				$extRP{$host} = "[CNAME record]|$extRP{$host}";
			    } else {
				#
				# Follow the CNAME chain.
				#
				$cname_loop = 0;
				if ($chain_length == 1) {
				    #
				    # Regardless of one's opinion about CNAME
				    # chains being bad practice or a feature,
				    # record this status as well as the current
				    # chain length until final resolution can
				    # be determined.  This program's default
				    # behavior is to treat chained CNAMEs as
				    # no problem as long as a non-CNAME RR is
				    # ultimately resolved.
				    #
				    $extCNAME{$cname} = "[CNAME chain ](1)|$extCNAME{$cname}";
				    $chainCNAME{$cname} = [ $cname ];
				}
				for ($k = 0; $k < $chain_length; $k++) {
				    #
				    # Before rushing off to follow the latest
				    # CNAME, make sure it's not one that we've
				    # already encountered in the current chain.
				    #
				    if ($chainCNAME{$cname}[$k] eq $cname_n) {
					$extCNAME{$cname} =~ s/^[^\|]+/[ CNAME loop ]\($chain_length\)/;
					$cname_loop = 1;
					last;
				    }
				}
				#
				# Regardless of whether or not we've discovered
				# a CNAME loop, maintain accuracy by updating
				# the chain length of the base CNAME and add
				# the latest CNAME to the list of chain members.
				#
				$extCNAME{$cname} =~ s/ \]\(\d+\)/ \]\($chain_length\)/;
				push @{ $chainCNAME{$cname} }, $cname_n;
				if ($nextbatch_is_open && !$cname_loop) {
				    #
				    # Find out where the chained CNAME leads us
				    # by creating the appropriate entry in the
				    # pending DiG batch file.
				    #
				    $buffer = "$cname_n ANY \%CNAME=$cname\n";
				    if (length($buffer) > $DiG_bufsize) {
					#
					# Avoid creating a buffer overrun in
					# DiG by splitting the necessary data
					# across two lines of batch input.
					#
					print NEXTBATCH "<NO-OP> ANY \%CNAME=$cname\n";
					print NEXTBATCH "$cname_n ANY \%CNAME\n";
				    } else {
					print NEXTBATCH $buffer;
					#
					# If a buffer overrun occurs despite
					# our efforts, the field most likely
					# to be trashed is the one holding the
					# value of "$cname".  As an exercise in
					# paranoid programming, we'll create
					# a reverse-lookup hash as a way to
					# recover the base CNAME in case our
					# fears are realized.
					#
					unless (exists($baseCNAME{$cname_n})) {
					    $baseCNAME{$cname_n} = $cname;
					} else {
					    #
					    # No can do.  Another base CNAME
					    # has already submitted a query to
					    # the current batch file for the
					    # same chained CNAME.  Not only do
					    # we have to give up the current
					    # backup attempt, the original base
					    # CNAME that got here first must
					    # also abandon its backup copy.
					    #
					    $baseCNAME{$cname_n} = "";
					}
				    }
				} else {
				    #
				    # The buck just stopped.
				    # Update the cosmetic control variable.
				    #
				    $max_chain_length = $chain_length;
				}
			    }
			} else {
			    #
			    # All is well - either an Address RR exists for the
			    # object of the NS, MX, or PTR RR or the CNAME RR
			    # points to something other than another CNAME.
			    #
			    if ($rrtype eq 'NS') {
				delete($extNS{$host});
			    } elsif ($rrtype eq 'MX') {
				delete($extMX{$host});
			    } elsif ($rrtype eq 'SRV') {
				delete($extSRV{$host});
			    } elsif ($rrtype eq 'PTR') {
				delete($extPTR{$host});
			    } elsif ($rrtype eq 'AFSDB') {
				delete($extAFSDB{$host});
			    } elsif ($rrtype eq 'RT') {
				delete($extRT{$host});
			    } elsif ($rrtype eq 'RP') {
				delete($extRP{$host});
			    } elsif ($show_chained_cnames && $extCNAME{$cname} =~ /\]\(\d+\)/) {
				$max_chain_length = $chain_length;
			    } else {
				delete($extCNAME{$cname});
			    }
			}
			$first_answer = 0;	# Ignore remaining output until
			$rrtype = $status = "";	# the next batch entry is read.
		    }
		}
		close(*NEXTBATCH) if $nextbatch_is_open;

		# BIND's observed limits of chasing CNAME chains are:
		#
		# * BIND 8 will chase 8 CNAMEs - if #8 points to a non-CNAME
		#   RR, you'll get the answer.   If #8 points to yet another
		#   CNAME, you'll get the 9 CNAMEs and the response code will
		#   be set to SERVFAIL.
		#
		# * BIND 9 will chase 16 CNAMEs - if #16 points to a non-CNAME
		#   RR, you'll get the answer.    If #16 points to yet another
		#   CNAME, you'll get the 17 CNAMEs with a NOERROR response
		#   code.
		#
		# The loop that follows chained CNAMEs will exhaust itself
		# naturally or when the 17th iteration is reached.
		#
		unlink($nextbatch) if -z $nextbatch;
		if ($chain_length == 17) {
		    unlink($nextbatch) unless $debug;
		    $nextbatch = "";
		    $max_chain_length = 17;
		}
		close(*DIGOUT);
		if ($debug) {
		    close(*DEBUGOUT);
		} else {
		    unlink($DiG_batch);
		    #
		    # Clean up any leftover debug file of DiG's output
		    # from a prior run if the -debug option was specified.
		    #
		    unlink($debug_file) if -e $debug_file;
		}
		$DiG_batch = $nextbatch;
	    }
	}
    }

    # Ideally, all of the hashes should be empty.
    # If not, here's where the warnings get reported.
    # Note: The `if' tests are structured to prevent short-circuiting so
    #       that there are no passed-over calls to the keys() function
    #       for initializing the iterator of each hash.
    #
    if ((keys(%NSlist) + keys(%extNS))) {
	print STDERR "${n}Warning: found NS RR(s) pointing to the following problematic domain name(s):\n";
	$n = "";
	while (($host, $tmp) = each %NSlist) {
	    #
	    # If DiG generates output that is unexpected in either content
	    # or sequence and it is detected by the `h2n' output parser,
	    # the value of the hash index may lack the assignment of the
	    # status field as the parser attempts to resynchronize.  
	    # In such cases, assign a generic status field that indicates
	    # roughly what happened.
	    #
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode && $location ne "(SOA MNAME)";
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extNS) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode && $location ne "(SOA MNAME)";
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%MXlist) + keys(%extMX))) {
	print STDERR "${n}Warning: found MX RR(s) pointing to the following problematic domain name(s):\n";
	$n = "";
	while (($host, $tmp) = each %MXlist) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extMX) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclSRV) + keys(%extSRV))) {
	print STDERR "${n}Warning: found SRV RR(s) pointing to the following problematic domain name(s):\n";
	$n = "";
	while (($host, $tmp) = each %spclSRV) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extSRV) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclPTR) + keys(%extPTR))) {
	print STDERR "${n}Warning: found PTR RR(s) pointing to the following problematic domain name(s):\n";
	$n = "";
	while (($host, $tmp) = each %spclPTR) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extPTR) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclAFSDB) + keys(%extAFSDB))) {
	print STDERR "${n}Warning: found AFSDB RR(s) pointing to the following problematic dom. name(s):\n";
	$n = "";
	while (($host, $tmp) = each %spclAFSDB) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extAFSDB) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclRT) + keys(%extRT))) {
	print STDERR "${n}Warning: found RT RR(s) pointing to the following problematic domain name(s):\n";
	$n = "";
	while (($host, $tmp) = each %spclRT) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extRT) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclRP) + keys(%extRP))) {
	print STDERR "${n}Warning: found RP RR(s) pointing to the following problematic TXT domain(s):\n";
	$n = "";
	while (($host, $tmp) = each %spclRP) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extRP) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
    }
    if ((keys(%spclCNAME) + keys(%extCNAME))) {
	print STDERR "${n}Warning: found CNAME(s) pointing to the following problematic domain name(s):\n";
	print STDERR "(numbers within parentheses represent the length of a CNAME chain)\n" if $max_chain_length;
	$n = "";
	if (!$max_chain_length || $verify_mode) {
	    $i = "";
	} else {
	    $i = ($max_chain_length < 10) ? "   " : "    ";
	}
	while (($host, $tmp) = each %spclCNAME) {
	    $host .= ".$domain" unless $host =~ /(^|[^\\])\.$/;
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $status .= ($status =~ /\)$/) ? "" : $i;
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	}
	while (($host, $tmp) = each %extCNAME) {
	    $tmp = "[sync. error ]|$tmp" unless $tmp =~ /\|/;
	    ($status, $location) = split('\|', $tmp, 2);
	    $status .= ($status =~ /\)$/) ? "" : $i;
	    $location = "" if $verify_mode;
	    printf STDERR "%s%s  %s\n", &TAB(" $host", $t), $status, $location;
	    if ($show_chained_cnames && $status =~ /\]\((\d+)\)/) {
		$chain_length = $1;
		for ($k = 1; $k <= $chain_length; $k++) {
		    print STDERR "  > $chainCNAME{$host}[$k]\n";
		}
	    }

	}
    }
    # Undefine data structures that are no longer needed so that
    # the hashes and lists in the NS-related audits that follow
    # have room to grow.
    #
    unless ($verify_delegations) {
	undef %NSlist;
	undef %extNS;
    }
    undef %MXlist;
    undef %extMX;
    undef %spclSRV;
    undef %extSRV;
    undef %spclPTR;
    undef %extPTR;
    undef %spclAFSDB;
    undef %extAFSDB;
    undef %spclRT;
    undef %extRT;
    undef %spclRP;
    undef %extRP;
    undef %spclCNAME;
    undef %extCNAME;
    undef %baseCNAME;
    undef %chainCNAME;

    if (keys(%NSowners)) {
	#
	# Check that each zone has at least two listed name servers
	# and, if so, that the TTLs of the NS RRset are consistent.
	# While we're at it, name servers in child zones will trigger
	# a check for necessary glue in this (the parent) zone.
	#
	$last_zone = "";
	@delete_list = ();
	while (($zone, $buffer) = each %NSowners) {
	    #
	    # Cycle through each zone.  Before plowing ahead, however,
	    # this is the time to identify any invalid "grandchild"
            # zones that may have come our way, e.g.,
	    #
	    #   $ORIGIN example.com.
	    #   @             IN SOA    ...
	    #   ...
	    #   child           IN NS    ns.child        ; valid delegation
	    #   ns.child        IN A     192.249.249.3   ; valid glue
	    #   child.child     IN NS    ns.child.child  ; invalid delegation
	    #                                            ; NS RR below zone cut
	    #   ns.child.child  IN A     192.249.249.4   ; invalid - non-glue
	    #                                            ; A RR below zone cut
	    #
	    # Up to now, these instances have been treated like any other
	    # domain in that DiG has been called to look up references to
	    # these out-of-zone domains when they have appeared in the RDATA
	    # fields of the record types that `h2n' scrutinizes.
	    # From now on, however, these out-of-context NS records have
	    # no meaning and will be removed from the %NSowners hash.
	    # They'll be reported a bit later as just another occurrence
	    # of non-glue below a zone cut.
	    #
	    if ($zone ne "$Domain.") {
		$status = 1;
		$subzone = $zone;
		while ($subzone =~ /(\\[.]|[^.])*\./) {
		    $subzone =~ s/(\\[.]|[^.])*\.//;	# remove leftmost label
		    if (exists($NSowners{$subzone})) {
			#
			# Our original "$zone" is a bogus delegation because of
			# an existing delegation between it and the parent zone.
			#
			push(@delete_list, $zone);
			$status = 0;
			last;
		    }
		}
		next unless $status;
	    }
	    $ns_count = 0;
	    while ($buffer) {
		#
		# For each zone, cycle through each listed name server.
		#
		($ttl, $host, $buffer) = split(' ', $buffer, 3);
		$ns_count++;
		if ($ns_count == 1) {
		    $answer = $ttl;
		} elsif ($status) {
		    unless ($ttl == $answer) {
			push(@NSrfc2181, $zone);
			$status = 0;
		    }
		}
		unless ($host =~ /(^|[^\\])\.$/) {
		    #
		    # The domain name of the name server is not absolute and
		    # thus may be located in the zone that is currently being
		    # iterated.  If so, make a search for the required glue RR.
		    #
		    # Be advised that your mileage may vary when this section
		    # is working in verify mode.  That's because a name server
		    # that started out with missing glue may have fetched the
		    # missing A, AAAA, and/or A6 records from some other
		    # name server during the course of a recursive query.
		    # Fetched glue is included in a zone transfer unless
		    # requested from a name server that has been configured
		    # with the "recursion no" *and* "fetch-glue no" options
		    # or the name server is running BIND 9.
		    # NOTE: Per RFC-1034, delegation effectively cancels
		    #       wildcard matching.  Therefore, a wildcard can
		    #       not be used as a glue record, e.g., the
		    #       following A record does *not* qualify as glue:
		    #
		    #         $ORIGIN example.com.
		    #         child      NS   ns.child
		    #         *.child    A    192.168.1.1
		    #
		    #       Unfortunately, not all name server implementations
		    #       treat this condition correctly.  BIND 4/8 will go
		    #       ahead and synthesize the following record in the
		    #       answer or additional section of a response:
		    #
		    #         ns.child.example.com.    A    192.168.1.1
		    #
		    #       This is related to the design limitation of all
		    #       served zones being contained in a single database.
		    #       BIND 9, however, strictly complies with the RFC
		    #       and will *not* match `ns.child.example.com.' with
		    #       the wildcard record in the parent zone.
		    #
		    ($subzone, $wildcard, $rrtype) = &MATCH_DOMAIN("$host.$domain");
		    if ($subzone && ("$zone.$domain" eq $subzone
				     || $zone eq "$Domain.")) {
			$match = 0;
			if (exists($Hosts{$host})) {
			    $match = 1;
			} elsif (exists($RRowners{$host})) {
			    $match = 1 if $RRowners{$host} =~ / (A|AAAA|A6) /;
			}
			if ($match) {
			    #
			    # Register the glue record so that it can be
			    # identified a bit later during the check for
			    # non-glue records at or below a zone cut.
			    #
			    $glue_RRs{$host} = 1;
			} else {
			    unless ($last_zone) {
				print STDERR "${n}Warning: found NS RR(s) to be missing the requisite glue record(s):\n \$ORIGIN $Domain.\n";
				$n = "";
			    }
			    if ($zone ne $last_zone) {
				$last_zone = $zone;
				$authority = ($zone eq "$Domain.") ? '@' : $zone;
				printf STDERR "%s\t%s\tIN NS\t%s\n", &TAB(" $authority", 16), $ttl, $host;
			    } else {
				printf STDERR "%s\t%s\tIN NS\t%s\n", &TAB(" ", 16), $ttl, $host;
			    }
			}
		    }
		}
	    }
	    push(@NSrfc1034, $zone) if $ns_count == 1;
	}
	# Now that we're no longer iterating over %NSowners, remove
	# the bogus delegations that were marked for deletion.
	#
	foreach $subzone (@delete_list) {
	    delete($NSowners{$subzone});
	}

	if (@NSrfc1034) {
	    print STDERR "${n}Warning: found zone(s) not having at least two listed name servers (RFC-1034):\n \$ORIGIN $Domain.\n";
	    $n = "";
	    foreach $zone (@NSrfc1034) {
		($ttl, $host) = split(' ', $NSowners{$zone});
		$authority = ($zone eq "$Domain.") ? '@' : $zone;
		printf STDERR "%s\t%s\tIN NS\t%s\n", &TAB(" $authority", 16), $ttl, $host;
	    }
	}

	if (@NSrfc2181) {
	    print STDERR "${n}Warning: found NS RRset(s) with inconsistent TTL values (RFC-2181):\n \$ORIGIN $Domain.\n";
	    $n = "";
	    foreach $zone (@NSrfc2181) {
		$buffer = $NSowners{$zone};
		$ns_count = 1;
		while ($buffer) {
		    ($ttl, $host, $buffer) = split(' ', $buffer, 3);
		    if ($ns_count == 1) {
			$authority = ($zone eq "$Domain.") ? '@' : $zone;
			printf STDERR "%s\t%s\tIN NS\t%s\n", &TAB(" $authority", 16), $ttl, $host;
		    } else {
			printf STDERR "%s\t%s\tIN NS\t%s\n", &TAB(" ", 16), $ttl, $host;
		    }
		    $ns_count++;
		}
	    }
	}
	# Undefine the lists that are no longer needed in order to provide
	# the "%non_glue_RRs" hash with the maximum amount of headroom.
	#
	undef @NSrfc1034;
	undef @NSrfc2181;
	undef @delete_list;

	# Starting with BIND 8.2.3 but not (yet) in BIND 9, master zones will
	# not load if non-glue, non-address resource records appear at or
	# below any zone cut.  In other words, no child-zone data is (should be)
	# permitted in the parent zone unless it is glue.
	# Now that all child zones and resource records are known and existing
	# glue has been identified, this inspection can proceed.
	#
	scalar(keys(%RRowners));
	while (($host, $rrtype) = each %RRowners) {
	    next if $host eq "$Domain.";
	    ($subzone, $wildcard, $tmp) = &MATCH_DOMAIN("$host.$domain");
	    if ($subzone) {
		if ("$host.$domain" eq $subzone) {
		    #
		    # We are at a zone cut.  Remove the NS RRtype
		    # and, if present, the KEY RRtype.
		    #
		    1 while $rrtype =~ s/ (NS|KEY) / /;
		}
		if (exists($glue_RRs{$host})) {
		    #
		    # This domain name exists as required glue - which is good.
		    # What's bad is if the domain name has a record type other
		    # than A, AAAA, or A6 (if we're still at a zone cut, the
		    # NS and KEY records have already been taken care of).
		    #
		    1 while $rrtype =~ s/ (A|AAAA|A6) / /;
		}
		next if $rrtype eq " ";
		$non_glue_RRs{$host} = $rrtype;
	    }
	}
	scalar(keys(%Hosts));
	while (($host, $tmp) = each %Hosts) {
	    $host = lc($host);		# in case the -P option is in effect
	    ($subzone, $wildcard, $tmp) = &MATCH_DOMAIN("$host.$domain");
	    if ($subzone) {
		#
		# Only A records of canonical names are represented in the
		# "%Hosts" hash.  These will either be glue or non-glue.
		#
		next if exists($glue_RRs{$host});
		$non_glue_RRs{$host} = " A ";
	    }
	}
	if (keys(%non_glue_RRs)) {
	    print STDERR "${n}Warning: found the following non-glue domain name(s) at or below a zone cut:\n \$ORIGIN $Domain.\n";
	    $n = "";
	    $warning = 0;
	    while (($host, $rrtype) = each %non_glue_RRs) {
		$match = 1;
		while ($rrtype =~ /\S/) {
		    $tmp = $rrtype;
		    $tmp =~ /^\s*(\S+)(.*)/;
		    $tmp = $1;
		    $rrtype = $2;
		    $warning = 1 unless $tmp =~ /^(A|AAAA)$/;
		    if ($match) {
			printf STDERR "%s%s\t...\n", &TAB(" $host", 32), $tmp;
			$match = 0;
		    } else {
			printf STDERR "%s%s\t...\n", &TAB(" ", 32), $tmp;
		    }
		}
	    }
	    $Load_Status = 3 if $warning && $BIND_version_num >= 80203
					 && $BIND_version_num < 90000;
	}
    }

    if ($n) {
	return 0;
    } else {
	print STDERR "\n" unless $verify_mode;
	return 1;
    }
}


#
# Do some basic sanity checks on the SOA timer values.  These checks
# are the same ones that BIND performs when a zone is loaded.
#
# Return values:
#   0 = no warnings
#   1 = warnings
#
sub CHECK_SOA_TIMERS {
    my ($expire, $expire_sec, $message, $n, $refresh, $refresh_sec);
    my ($retry, $retry_sec, $ttl, $ttl_sec);

    # If the READ_RRs subroutine detected a bad or missing SOA timer,
    # there is no point in proceeding.  Exit with a successful return
    # code since the error has already been reported.
    #
    return 0 unless $valid_SOA_timers;

    $refresh = ($Refresh) ? $Refresh : $DefRefresh;
    $retry   = ($Retry) ? $Retry : $DefRetry;
    $expire  = ($Expire) ? $Expire : $DefExpire;
    if ($Ttl) {
	$ttl = $Ttl;
    } else {
	$ttl = ($rfc2308) ? $DefNegCache : $DefTtl;
    }
    $refresh_sec = &SECONDS($refresh);
    $retry_sec = &SECONDS($retry);
    $expire_sec = &SECONDS($expire);
    $ttl_sec = &SECONDS($ttl);
    $message = "";

    if ($expire_sec < ($refresh_sec + $retry_sec)) {
	$message = " SOA expire value is less than SOA refresh + retry\n   [$expire < $refresh + $retry]";
    }
    if ($expire_sec < ($refresh_sec + (10 * $retry_sec))) {
	$n = ($message) ? ".\n" : "";
	$message .= "$n SOA expire value is less than SOA refresh + (10 * retry)\n   [$expire < $refresh + (10 * $retry)]";
    }
    if ($expire_sec < (7 * 24 * 3600)) {
	$n = ($message) ? ".\n" : "";
	$message .= "$n SOA expire value ($expire) is less than 7 days";
    }
    if ($expire_sec > (183 * 24 * 3600)) {
	$n = ($message) ? ".\n" : "";
	$message .= "$n SOA expire value ($expire) is greater than 6 months";
    }
    if ($refresh_sec < (2 * $retry_sec)) {
	$n = ($message) ? ".\n" : "";
	$message .= "$n SOA refresh value is less than SOA retry * 2 [$refresh < ($retry * 2)]";
    }
    if (!$verify_mode && $rfc2308 && $ttl_sec > 10800) {
	$n = ($message) ? ".\n" : "";
	$message .= "$n SOA negative cache value ($ttl) exceeds recommended maximum of 3 hours";
    }
    if ($message) {
	if ($verify_mode) {
	    print STDERR "\nWarning: found the following problematic SOA time interval(s):\n";
	} else {
	    print STDERR "Warning: the -o/+t option values generated the following message(s):\n";
	}
	print STDERR "$message.\n";
	return 1;
    } else {
	return 0;
    }
}


#
# Perform various consistency checks on zone data for each domain
# specified with the -V option.  The zone transfer data must first
# be processed by the "READ_RRs" subroutine before this one is called.
# Checks are made for the following conditions:
#   * SOA records containing time specifications with extreme values
#     (via the "CHECK_SOA_TIMERS" subroutine).
#   * NS, MX, and PTR records that point to CNAMEs or domain names
#     with no Address records or to nonexistent domain names (via the
#     "AUDIT_RRs" subroutine).
#   * CNAME records that point to nonexistent domain names, i.e.,
#     "dangling" CNAMEs (via the "AUDIT_RRs" subroutine).
#   * Zones with only one listed name server (violates RFC-1034),
#     NS RRsets with inconsistent TTL values (violates RFC-2181),
#     and NS RRs with missing glue (via the "AUDIT_RRs" subroutine).
#   * Lame delegations and name servers that are not running
#     or are unresponsive.  Accomplished with the `check_del'
#     program but only if delegation checking is not purposely
#     disabled by specifying the `-no-check-del' option.
#
# NOTE: In order to not adversely affect the amount of time that
#       `h2n' takes in its normal task of generating zone data,
#       future consistency checks should be limited to the -V option by
#       placing them into this subroutine instead of "AUDIT_RRs".
#
# Return values:
#   0 = no warnings
#   1 = warnings
#
sub CHECK_ZONE {
    my ($warning_status) = @_;
    my ($answer, $buffer, $debug_file, $del_batch, $domain, $first_answer);
    my ($fq_host, $host, $i, $n, $t, $ttl, $zone);
    my (%check_del_RRs, @zone_rrset, @sorted_rrset, $ns_data, $mismatch);


    if ($BIND_ver_msg) {
	print STDERR "\nWarning: the name server supplying the zone data is running a version\n         of BIND that may be vulnerable to the following bug(s):\n";
	print STDERR "$BIND_ver_msg\n";
    }

    # First do some sanity checks on the SOA timer values and then call
    # the "AUDIT_RRs" subroutine so that the %NSlist and %extNS data
    # structures can be referenced.  These hashes are needed to prepare
    # an appropriate list of name servers for which proper delegation is
    # to be checked.
    # 
    $n = (&AUDIT_RRs(&CHECK_SOA_TIMERS)) ? "" : "\n";

    # Per RFC-1034, the NS RRsets that surround a zone cut are required
    # to be kept consistent.  We will now check for this by comparing
    # the NS RRset from the original DNS query (ostensibly above the zone
    # cut) to the NS RRset obtained from the zone transfer data, i.e.,
    # below the zone cut).  Our mileage may vary, however, because the
    # name server(s) that supplied the answer to the recursive DNS query
    # may have discovered and replaced the less credible NS RRset above
    # the zone cut with the authoritative (and thus more credible) NS
    # RRset from the zone itself.
    # 
    # Note: As previously mentioned in the main program, a future
    #       version of `h2n' will use a query strategy that
    #       attempts to consistently obtain the NS RRset of the
    #       delegating parent zone (above the zone cut).  However,
    #       even this strategy may not succeed under the following
    #       conditions:
    #
    #         1. The name server is running BIND 4 or 8.
    #         2. The same name server is authoritative for the child
    #            zone as well as the parent zone, i.e., both sides
    #            of the zone cut.
    #
    #       In this case, the authoritative NS RRset of the child
    #       zone will always be returned.  This is due to the fact
    #       that BIND 4 and 8 name servers have just one database
    #       for storing data.  Both NS RRsets can't occupy the same
    #       space and so the authoritative data always wins.
    #       Another side-effect of this scenario is if the parent
    #       zone is being verified.  Missing glue will not be detected
    #       for the same reason.  It will always be "filled in" by the
    #       presence of the child zone's authoritative data.
    #       BIND 9 name servers, in contrast, have separate internal
    #       databases for each zone and, thus, exhibit none of these
    #       anomalies.
    # 
    #
    $domain = ($Domain) ? "$Domain." : "";	# accommodate the root zone
    ($ns_data = $NSowners{"$Domain."}) =~ s/ \d+ (\S+)/$1 /g;
    $ns_data =~ s/([^.]|\\[.]) /$1.$domain /g;
    @zone_rrset = split(' ', $ns_data);
    @sorted_rrset = sort { $a cmp $b } @zone_rrset;
    @zone_rrset = @sorted_rrset;
    @sorted_rrset = sort { $a cmp $b } @DNSrrset;
    @DNSrrset = @sorted_rrset;
    $mismatch = 0;
    unless (scalar(@DNSrrset) == scalar(@zone_rrset)) {
	$mismatch = 1;
    } else {
	for ($i = 0; $i < scalar(@DNSrrset); $i++) {
	    if ($DNSrrset[$i] ne $zone_rrset[$i]) {
		$mismatch = 1;
		last;
	    }
	}
    }
    if ($mismatch) {
	print STDERR "${n}Warning: found inconsistent NS RRsets surrounding the zone boundary (RFC-1034):\n";
	$n = "";
	$t = (length(" $Domain.") <= 20) ? 16 : 24;
	for ($i = 0; $i < @DNSrrset; $i++) {
	    if ($i == 0) {
		printf STDERR "%s\tIN NS\t%s\n", &TAB(" $Domain.", $t), $DNSrrset[$i];
	    } else {
		printf STDERR "%s\tIN NS\t%s\n", &TAB(" ", $t), $DNSrrset[$i];
	    }
	}
	print STDERR " (non-authoritative)\n";
	print STDERR " ---------------------------- zone cut ----------------------------\n";
	print STDERR " (  authoritative  )\n";
	for ($i = 0; $i < @zone_rrset; $i++) {
	    if ($i == 0) {
		printf STDERR "%s\tIN NS\t%s\n", &TAB(" \@", $t), $zone_rrset[$i];
	    } else {
		printf STDERR "%s\tIN NS\t%s\n", &TAB(" ", $t), $zone_rrset[$i];
	    }
	}
    }

    if (keys(%NSowners) && $verify_delegations) {
	#
	# First, create the input file that will
	# be needed by the `check_del' program.
	#
	$del_batch = "$debug_DIR/h2n-del.bat_$data_fname";
	unless (open(*DELBATCH, "> $del_batch")) {
	    print STDERR "Couldn't create batch file for `check_del': $!\n";
	    print STDERR "Unable to verify name server delegations.\n";
	    $verify_delegations = 0;
	} else {
	    #
	    # Be thorough by also checking the NS RRset
	    # of the parent domain's delegation.
	    #
	    for ($i = 0; $i < @DNSrrset; $i++) {
		print DELBATCH "$Domain.\t\t\tIN NS\t$DNSrrset[$i]\n";
		$check_del_RRs{"$Domain."} .= " $DNSrrset[$i] ";
	    }
	    $domain = ($Domain) ? "$Domain." : "";  # accommodate the root zone
	    while (($zone, $buffer) = each %NSowners) {
		#
		# Cycle through each zone.
		#
		$zone = "$zone.$domain" if $zone !~ /(^|[^\\])\.$/;
		while ($buffer) {
		    #
		    # For each zone, cycle through each listed name server.
		    #
		    ($ttl, $host, $buffer) = split(' ', $buffer, 3);
		    if ($host =~ /(^|[^\\])\.$/) {
			$fq_host = $host;
		    } else {
			$fq_host = "$host.$domain";
		    }
		    # Only check name servers that have a reasonable
		    # expectation of being found.
		    #
		    unless ((exists($NSlist{$host}) && $NSlist{$host} !~ /CNAME /) ||
			    (exists($extNS{$fq_host}) && $extNS{$fq_host} !~ /CNAME /)) {
			#
			# Add the NS RR for `check_del' to process 
			# but only if it is not already registered.
			#
			unless (exists($check_del_RRs{$zone}) &&
				$check_del_RRs{$zone} =~ / $fq_host /) {
			    print DELBATCH "$zone\t\t$ttl\tIN NS\t$fq_host\n";
			    $check_del_RRs{$zone} .= " $fq_host ";
			}
		    }
		}
	    }
	    close(*DELBATCH);
	}

	if ($verify_delegations && -s $del_batch) {
	    $first_answer = $answer = 1;
	    $debug_file = "$debug_DIR/h2n-del.ans_$data_fname";
	    if ($debug) {
		unless (open(*DEBUGOUT, "> $debug_file")) {
		    print STDERR "Error opening `$debug_file': $!\n";
		    print STDERR "Disabling the -debug option for the remainder of the program.\n";
		    $debug = 0;
		}
	    }
	    #
	    # Use the `-F' (Fast) argument of `check_del'.
	    # Otherwise, you'll be sorry (and bored) when it
	    # trudges through a large list of unresponsive servers.
	    #
	    unless (open(*DELOUT, "$check_del -F -v -f $del_batch 2>&1 |")) {
		print STDERR "Error running the `check_del' program: $!\n";
		print STDERR "Unable to verify NS delegations.\n";
	    } else {
		while (<DELOUT>) {
		    print DEBUGOUT $_ if $debug;
		    next if /^$|^Skipping | is authoritative | (moved|put).* list/;
		    $answer = 0 if /^\s*\d/;	# Ignore everything past the
						# "proper" & "improper" summary.
		    #
		    # If this point is reached, then `check_del' has found
		    # something noteworthy to report.
		    #
		    if ($answer) {
			if ($first_answer) {
			    print STDERR "${n}Warning: verifying the NS delegations generated the following error(s):\n";
			    $n = "";
			    $first_answer = 0;
			}
			print STDERR " $_";
		    }
		}
		close(*DELOUT);
	    }
	    if ($debug) {
		close(*DEBUGOUT);
	    } else {
		unlink($del_batch);
		#
		# Clean up any leftover debug file of check_del's output
		# from a prior run if the -debug option was specified.
		#
		unlink($debug_file) if -e $debug_file;
	    }
	}
    }

    if ($n) {
	$n = "Verification completed.\n";
	if ($BIND_ver_msg) {
	    $n = "\n$n";
	    $warning_status = 1;
	}
    }
    if ($warning_status || !$n) {
	print STDERR "$n\n";
	return 1;
    } else {
	return 0;
    }
}


sub VERIFY_ZONE {
    my ($NS, $additional, $answer, $answer_section, $attempt, $authority);
    my ($displayed_domain, $domain, $error, $expected_SOA_count, $flags);
    my ($i, $ip, $n, $ns, $origin, $query_options, $separator, $status);
    my ($version_buffer, $warning_status, $zone_data);
    my (@NSlocal, @NSnet, @NSother, @NSsubnet, @Nameservers);

    #
    # Verify the zone data for each domain in the @Vdomains array.
    #
    # It only makes sense to do all this work if the "$verbose" and "$audit"
    # flags are enabled.  However, the user is free to set the level of
    # name checking by choosing the appropriate -I option which will then
    # set the "$rfc1123" flag accordingly.  "Strict" name-checking will
    # be disabled, however, since the RFC-952 check for single-character
    # hostnames and/or aliases is only valid when processing a host table.
    #
    $verbose = 1;
    $audit = 1;
    $rfc952 = 0;

    &GET_LOCAL_NETINFO;			# for determining best net connectivity
    $separator = "";
    while ($domain = pop(@Vdomains)) {
	#
	# Certain characters on the `h2n' command line like "<|>&()$?;'`"
	# must be escaped in order to make it past the shell and into this
        # program.  If any of these are present, the "$domain" variable will
	# still keep them in order to make it past the shell once again when
	# the Perl system() function is called to make the DiG AXFR query a
	# bit later in this subroutine.
	# Such escapes and shell operator characters, however, are poisonous
	# when trying to use them in a temporary filename.  To solve this
        # problem, we'll copy "$domain" into "$data_fname" so that these
	# troublesome characters can be translated into something harmless.
	# We'll start by cleaning up the "$data_fname" variable just enough
	# so that we can display the actual domain name being verified.
	# NOTE 1: Leave "\$" and "\@" (and possibly "\(" and "\)" - see NOTE 2)
	#         escaped as an accurate presentation of what BIND itself
	#         displays when encountering these characters.
	#         In fact, "@" may appear unescaped on the command line
	#         since it is not a shell special character.  If that's
	#         the case, we'll insert an escape character before each
	#         unescaped "@" for the sake of robustness and consistency.
	# NOTE 2: The is an inconsistency in the way that BIND8 and early
	#         versions of BIND9 handle the "()" characters in the owner
	#         field of a domain name as opposed to BIND 9.1.2 and later
	#         versions.  BIND versions less than 9.1.2 will load the
	#         zone with or without preceding escapes and the corresponding
	#         versions of DiG will strip the escape characters that precede
	#         the "()" characters when it displays answers to a query.
	#         Conversely, BIND 9.1.2 and later will not load a zone with
	#         unescaped "()" characters in an owner field and the
	#         corresponding versions of DiG always include the escape
	#         characters in its displayed answers even when querying a
	#         name server that is running a BIND version earlier than 9.1.2.
	#
	$domain =~ s/([^\\])@/$1\\@/g;
	$data_fname = $domain;
	if ($DiG_version_num >= 90102) {
	    $data_fname =~ s/\\([<|>&\?;'`])/$1/g;
	} else {
	    $data_fname =~ s/\\([<|>&\(\)\?;'`])/$1/g;
	}
	print STDOUT "$separator";
	print STDOUT "\nVerifying zone data for domain `$data_fname':\n";
	print STDOUT "Getting NS RRset...\n";
	#
	# Occasionally, a query is made for a domain's NS RRs and the
	# Additional Section of the response is incomplete.  One or more
	# subsequent queries, however, do return the IP address(es) that
	# that correspond with the NS RR(s) in the Answer Section.
	# The most likely scenario is an NS RRset where one or more NS RRs
	# point to name servers that are in a different Top-Level Domain
	# (TLD) than the zone itself.  Since the Internet root name servers
	# do not perform recursion and do not fetch glue, Address RRs that
	# are not on the same TLD server as the NS RRs will not appear in
	# the Additional Section of the response.  Once the local name server
	# has the NS RRset cached, successive recursive queries will cause
	# the desired Address RR(s) to appear in the Additional Section (but
	# *only* if the option `fetch-glue' is set to `yes' (see Note 3).
	# In anticipation of this possibility, we'll make up to five attempts
	# to get the Additional Section information that we expect to find.
	#
	# NOTE 1: Some name servers give out minimal answers, i.e., empty
	#         Authority and Additional Sections.  BIND 9 even has a
	#         configuration option to do this ("minimal-responses yes").
	#         The current strategy fails miserably in this circumstance.
	#
	# NOTE 2: If all of the NS records are misconfigured to point to
	#         CNAMEs instead of the canonical names (with A RRs), the
	#         Additional Section will always be empty since CNAMEs
	#         aren't "chased" when processing that section of the
	#         response.  The current strategy fails miserably in this
	#         circumstance.  See NOTE 3.
	#
	# NOTE 3: BIND 4 and 8 name servers have the `fetch-glue' option
	#         enabled by default.  BIND 9 name servers, however, have
	#         deprecated the glue-fetching option because it is now
	#         considered a bad idea.
	#         A future version of `h2n' will use a query strategy
	#         that tries to consistently get the delegation NS RRset
	#         from the parent zone (useful for later comparison with
	#         the authoritative NS RRset of the child zone).
	#         Also, explicit queries will be made for the Address RRs
	#         of the NS RRs.  Thus, the reliance on glue-fetching for
	#         supplying Address RRs in the Additional section will no
	#         longer be necessary.
	#
	$query_options = "+noques +noauthor +nostats +$DiG_timeout +$DiG_retries";
	@NSlocal = @NSsubnet = @NSnet = @NSother = ();
	$match = $error = 0;
	$attempt = 1;
	until ($match || $error || $attempt > 5) {
	    sleep 2 if $attempt > 1;
	    unless (open(DIGOUT, "$DiG $query_options $domain NS 2>&1 |")) {
		print STDERR "Error running the `DiG' program: $!\n";
		print STDERR "I give up ... sorry.\n";
		exit(2);
	    }
	    while (<DIGOUT>) {
		chop;
		next if /^$/;
		#
		# Whenever `h2n' calls the DiG utility, the pattern-matching
		# statements for processing the output are structured to be
		# compatible with the different formats generated by versions
		# 2.X, 8.X, and 9.X of DiG.
		#
		if (/^; <<>> DiG/) {
		    $status = "";
		    $answer = $answer_section = 0;
		    @DNSrrset = ();
		} elsif (/^;.+(connection |no route|unreachable)/i) {
		    s/[^:]+:/ /;
		    $message = "DiG reported the following error:\n$_";
		    $error = 1;
		} elsif (/^;.+HEADER.+opcode: QUERY, status: ([^,]+)/) {
		    $status = $1;
		    if ($status ne 'NOERROR') {
			$message = "DiG reported the following status: $status";
			$error = 1;
			$status = "";
		    }
		} elsif ($status && /^;.+flags: (.*); QUE.*, ANS[^:]*: (\d+), AUTH[^:]*: (\d+), ADDIT[^:]*: (\d+)/i) {
		    $flags = $1;
		    $answer = $2;
		    $authority = $3;
		    $additional = $4;
		    if ($answer == 0) {
			if ($flags !~ /aa/ && $authority == 0 && $additional == 0) {
			    #
			    # We've probably queried a name server that is
			    # lame due to either bad delegation or it has
			    # invalidated a zone due to bad data (although
			    # SERVFAIL would be the expected response for
			    # the latter case).
			    #
			    $message = "DiG reported a failed query.  Perhaps a lame delegation was encountered.";
			} else {
			    #
			    # We've encountered an undelegated domain name.
			    #
			    $message = "DiG reported that no NS records exist.";
			}
			$error = 1;
			$status = "";
		    } elsif (($additional < $answer) && ($attempt < 5)) {
			#
			# Quit parsing this query and initiate another
			# recursive one in an attempt to get at least
			# as many name server IP addresses in the
			# Additional section as there are name servers
			# in the Answer section.
			#
			last;
		    } else {
			#
			# We either have at least as many IP addresses as
			# there are name servers in the Answer section or
			# we are on our last attempted query.  Continue
			# parsing the current response.
			#
			next;
		    }
		}
		next unless $status;
		if ($answer > 0) {
		    if (/^;; ANSWER/) {
			$answer_section = 1;
			next;
		    } elsif (/^;; ADDITIONAL/) {
			$answer_section = 0;
			next;
		    } elsif ($answer_section) {
			#
			# Store the answers so that the NS RRset of the
			# response can be compared to the NS RRset that
			# is actually contained in the domain's zone data.
			#
			s/.+\s+//;
			push(@DNSrrset, lc($_));
			next;
		    }
		    # The Additional Section of the response has been reached.
		    # Assign the IP address to the proper network category.
		    #
		    next if !/\.\s.*\sA\s+/; 	# only deal with IPv4 A records
		    s/\.\s.*\sA\s+/ /;
		    $tmp = $_;
		    $tmp =~ s/.+ //;
		    $addr = pack('C4', split('\.', $tmp, 4));
		    $match = 0;
		    foreach $tmp (@our_addrs) {
			if ($addr eq $tmp) {
			    #
			    # The localhost is authoritative for the domain.
			    # Naturally, we'll try this IP address first.
			    #
			    push(@NSlocal, $_);
			    $match = 1;
			    last;
			}
		    }
		    next if $match;
		    for ($i = 0; $i < @our_netbits; $i++) {
			if (($addr & $our_netbits[$i]) eq $our_nets[$i]) {
			    #
			    # The IP address is on a local network.
			    # Now see if it's on a local subnet.
			    #
			    if (($addr & $our_subnetmasks[$i]) eq $our_subnets[$i]) {
				push(@NSsubnet, $_);
			    } else {
				push(@NSnet, $_);
			    }
			    $match = 1;
			    last;
			}
		    }
		    next if $match;
		    push(@NSother, $_);
		    $match = 1;
		}
	    }
	    close(DIGOUT);
	    $attempt++;
	}
	if ($error || !$match) {
	    $message = "Failed to obtain any name server IP addresses." unless $error;
	    $answer = 0;
	    $error = 1;
	}
	# Finish the cleanup of "$data_fname" so that our temporary files
	# can be built without generating an error.  Obnoxious filename
	# characters will get translated into a harmless "%".  Escaped
	# whitespace will be converted to underscore characters and then
	# any remaining escapes will be eliminated.
	#
	for ($data_fname) {
	    s/\\([\$@\(\)])/$1/g;	  # unescape the "$@()" characters
	    s/[\/<|>&\[\(\)\$\?;'`]/%/g;
	    s/\\\s/_/g;
	    s/\\//g;
	    s/(\S+)\.$/$1/;		  # trim last dot if not root zone
	}
	$zone_data = "$debug_DIR/h2n-zone.data_$data_fname";
	if ($debug && -s $zone_data) {
	    if ($error) {
		#
		# Even though we'll still proceed with the (re)verification
		# of the zone data left over from a previous run with the
		# `-debug' option, report the error that was encountered
		# when we tried to do a DNS query for the domain's NS RRset.
		# This will explain the subsequent warning message about
		# inconsistent NS RRsets which will surely come later.
		#
		print STDERR "$message\n";
		print STDERR "(proceeding anyway using zone data in `$zone_data')\n";
		$error = 0;
	    } else {
		print STDOUT "(using existing zone data in `$zone_data')\n";
	    }
	    $answer = $expected_SOA_count = 1;
	    $BIND_version = "";
	} elsif ($answer) {
	    print STDOUT "Transferring zone..";
	    $version_buffer = "";
	    $query_options = "+$DiG_timeout +$DiG_retries";
	    #
	    # Proactively create a generic error message
	    # just in case something unexpected happens.
	    #
	    $message = "All zone transfer attempts failed.";
	    $answer = 0;
	    $expected_SOA_count = 2;
	    @Nameservers = ();
	    push(@Nameservers, @NSlocal) if @NSlocal;
	    push(@Nameservers, @NSsubnet) if @NSsubnet;
	    push(@Nameservers, @NSnet) if @NSnet;
	    push(@Nameservers, @NSother) if @NSother;
	    foreach $NS (@Nameservers) {
		($ns, $ip) = split(' ', $NS, 2);
		print STDOUT ".";
		$version_buffer .= " ";
		$status = 0xffff & system("$DiG $query_options $domain axfr \@$ip > $zone_data 2>&1");
		#
		# If an error occurs, the message will be stored in a
		# variable and the next name server will be tried.
		# The loop is exited when a successful zone transfer is
		# made or there are no more name servers to try.  If the
		# transfer is unsuccessful, the last error message to be
		# stored will be the one that gets displayed.
		#
		if ($status == 0xff00) {
		    $message = "DiG command failed: $!";
		} elsif ($status > 0x80) {
		    $status >>= 8;
		    $message = "DiG command returned non-zero exit status: $status";
		} elsif ($status != 0) {
		    $message = "DiG command exited with ";
		    if ($status &   0x80) {
			$status &= ~0x80;
			$message .= "coredump from ";
		    }
		    $message .= "signal: $status";
		}
		#
		# Regardless of the system() function's exit status,
		# try to examine the last few lines of DiG's output.
		# We'll either get initial confirmation of a successful
		# zone transfer or, possibly, a more detailed message
		# explaining why the attempt(s) failed.
		# The definitive test of a successful zone transfer
		# will be the detection of the trailing SOA record
		# by the READ_RRs subroutine.
		#
		open(ZONE_DATA, "tail -7 $zone_data |");
		while (<ZONE_DATA>) {
		    if (/^;; Received (\d+) answers? \(\d+ records?\)/ ||
			/^;; XFR size:\s+(\d+) names?,\s+\d+ rrs?/i ||
			/^;; XFR size:\s+(\d+) records?/i) {
			#
			# These lines are output by pre-9.X, 9.0.X, and
			# 9.1.X-to-current versions of DiG, respectively,
			# and usually indicate a successful zone transfer.
			#
			$answer = $1;
			if ($answer == 0) {
			    #
			    # Pre-9.X versions of DiG return an answer count
			    # of zero to indicate a disallowed zone transfer.
			    #
			    $message = "Transfer of zone data is disallowed or unavailable.";
			}
			last;
		    } elsif (/^; Transfer failed/) {
			#
			# This line is returned by 9.X versions of DiG when a
			# zone transfer is either disallowed or unavailable.
			#
			$message = "Transfer of zone data is disallowed or unavailable.";
			last;
		    } elsif (/^;; [Cc]onnect/) {
			#
			# This pattern matches connection failures by all
			# versions of DiG.
			#
			s/^;; *//;
			s/[^:]+: //;
			chop;
			$message = ucfirst($_);
			$message .= "." unless $message =~ /\.$/;
			last;
		    }
		}
		close(ZONE_DATA);
		last if $answer > 0;
	    }
	    if ($answer > 0) {
		#
		# Report the name server from which the zone transfer was
		# obtained and make an inquiry about the version of BIND
		# it is running.
		#
		print STDOUT " (from `$ns' [$ip])\n";
		&GET_BIND_VERSION($ip);
	    }
	}
	if ($answer == 0) {
	    $n = ($error) ? "" : "\n";
	    print STDERR "${n}$message\n";
	    print STDERR "Unable to verify this domain.\n\n";
	    $separator = "";
	} else {
	    #
	    # Initialize the appropriate global variables that
	    # might be holding data from a previous pass.
	    #
	    # NOTE: Certain characters on the command line like "<|>&()?'`"
	    #       may have had to be escaped in order to make it past the
	    #       shell but require no escapes as part of a DNS domain name
	    #       (except for the previously-mentioned difference in the
	    #       way that "()" is handled by BIND/DiG versions 8 and 9).
	    #
	    #       Now that we're in a safer environment, remove the escapes
	    #       from "$domain".  Otherwise, what we think is the domain
	    #       name won't match what's in the actual zone data file.
	    #
	    #       The "$Domainpattern" variable is another story.  Since
	    #       it represents "$domain" as a matching Regular Expression,
	    #       we must make sure that any RE metacharacters contained
	    #       therein get escaped.
	    #
	    if ($DiG_version_num >= 90102) {
		$domain =~ s/\\([<|>&\?'`])/$1/g;
	    } else {
		$domain =~ s/\\([<|>&\(\)\?'`])/$1/g;
	    }
	    $Domain = $origin = $domain;
	    $Domain =~ s/\.$//;
	    $Domainpattern = ($domain ne '.') ? ".$Domain" : "";
	    $Domainpattern =~ s/([.\|\\\$\^\+\[\(\)\?'`])/\\$1/g;
	    $SOA_count = 0;
	    $RespHost = $RespUser = "";
	    $Serial = $Refresh = $Retry = $Expire = $Ttl = "";
	    %RRowners = ();
	    %NSowners = ();
	    %NSlist = ();
	    %extNS = ();
	    %Wildcards = ();
	    $newline_printed = 0;
	    if ($BIND_version) {
		$version_buffer .="(NS BIND version: $BIND_version)";
	    } else {
		$version_buffer = "";
	    }
	    print STDOUT "Parsing zone data...$version_buffer\n";
	    $warning_status = $newline_printed = &READ_RRs($zone_data, $origin,
							   $origin, $origin, 0);
	    print STDERR "\n" while $newline_printed--;
	    if ($SOA_count < $expected_SOA_count) {
		if ($expected_SOA_count == 2) {
		    print STDERR "Incomplete zone transfer detected - suppressing further action.\nUnable to verify this domain.\n\n";
		} else {
		    print STDERR "Missing SOA record from zone data - suppressing further action.\nUnable to verify this domain.\n\n";
		}
		$warning_status = 1;
	    } elsif (!exists($NSowners{$origin})) {
		print STDERR "No NS records found at zone top - suppressing further action.\nUnable to verify this domain.\n\n";
		$warning_status = 1;
	    } elsif ($Load_Status == 4) {
		#
		# Unbalanced quotes/parentheses prevented READ_RRs() from
		# completely reading the zone file.  The detailed error
		# message has already been printed.
		#
		print STDERR "Unable to verify this domain.\n\n";
		$warning_status = 1;
	    } else {
		print STDOUT "Performing in-zone and external lookups...\n";
		$warning_status = &CHECK_ZONE($warning_status);
	    }
	    if ($warning_status) {
		$separator = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n";
	    } else {
		print STDOUT "Domain verified with no detected improprieties!\n\n";
		$separator = "";
	    }
	}
	unlink($zone_data) unless $debug;
    }
    return;
}


#
# Reverse the octets of a network specification or the labels of
# a domain name.  Only unescaped "." characters are recognized
# as octet/label delimiters.
#
sub REVERSE {
    my ($dotted_token) = @_;

    $dotted_token =~ s/([^\\])[.]/$1 /g;
    return join('.', reverse(split(' ', $dotted_token)));
}


sub GIVE_UP {

    print STDERR "I give up ... sorry.\n";
    exit(2);
}


#
# Subroutine to parse a list of `h2n' options and arguments thereof.
#
# It is first called in a scalar context by the READ_RCFILE subroutine
# to process any options that are found in a special configuration file.
# In this context, PARSE_ARGS will print a message for each warning or
# error it encounters but will not terminate `h2n' prematurely.
# After returning a count of printed messages to READ_RCFILE, `h2n'
# will terminate with an informational message about the configuration
# file if the message count is non-zero.
#
# If no error or warning messages were encountered by READ_RCFILE,
# PARSE_ARGS is called again, this time by the main program in a
# void context to process the actual command-line arguments.
# This allows for a logical way to dynamically override any option
# that may be in the `h2n' configuration file as a normal default.
# In the void context, the program will terminate upon encountering
# any hard error while processing a command-line option.
# 
sub PARSE_ARGS {
    my @args = @_;
    #
    # The following two variables hold patterns for all the command-line
    # options that `h2n' recognizes.  By explicitly defining these
    # reserved tokens, the parser will be able to accept non-matching
    # tokens as option arguments which themselves may also begin with
    # a `-' or `+' character.
    # 
    my $h2n_opts = "-[AaBbCcdefHhIiLMmNnOoPpqrSsTtuVWwyZz]|-v([:=].*)?|"
		 . "\\+([CcLmOSt]|o[ms])";
    my $verify_opts = "-(no-?)?(check|verify)[_-]?del|-(no-?)?debug(:.*)?|"
		    . "-(no-?)?recurs(e|ive|ion)|"
		    . "-(no-?)?show-?nxdomain-?cnames?|"
		    . "-(no-?)?show-?(cname|chained)-?(chain|cname)s?";

    # Create a set of RRtypes that will be recognized as valid to add
    # to a forward-mapping zone's apex with the -T option.
    #
    my $apex_RRtypes = "MX|A|PTR|AAAA|HINFO|RP|TXT|SRV|A6|NSAP|AFSDB|RT|ISDN|"
		     . "X25|PX|NAPTR|LOC|CERT|SIG|KEY|KX|DNAME|WKS|GPOS|APL";

    my ($AltDb, $AltSpcl, $IP_range, $already_warned, $argument, $arg2, $char);
    my ($cidr_size, $classC_key, $comment, $continuation_line);
    my ($default_supernetting, $domain_arg, $error, $file, $formatted_template);
    my ($i, $j, $last_char, $lastNorD, $lc_zone_name, $line_num);
    my ($message_count, $net, $net_file, $octet1, $octets1_and_2, $octet_token);
    my ($open_paren_count, $open_quote, $option, $original_line, $overlap);
    my ($preference, $ptr_arg, $ptr_map, $rfc2317_domain, $rdata, $rrtype);
    my ($skip_next_token, $sub_classC_key, $subnet, $subnet_key, $subnetmask);
    my ($supernet, $tmp1, $tmp2, $tmpMasterTtl, $tmpTtl, $token, $ttl);
    my (%allocated_octets, %duplicate, %e_opt_domain, %sub_classC);
    my (@file_args, @ctime, @unbalanced_args);

    $lastNorD = "";
    $default_supernetting = $i = $message_count = 0;
    while ($i <= $#args) {
	$option = $args[$i];

	if ($option eq "-A") {
	    $do_aliases = 0;

	} elsif ($option eq "-a" || $option eq "-n") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper $option option; at least one argument, a network number, is required.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $lastNorD = "-n" if $option eq "-n";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(mode|domain|ptr-owner)=/i) {
		    if ($argument =~ /^mode=/i) {
			$argument =~ s/=.*/=/;
			print STDERR "Improper $option option.\nA network number must precede `$argument'.\n";
		    } elsif ($option eq "-n") {
			print STDERR "Improper -n option.\nA network number must precede `$argument'.\n";
		    } else {
			print STDERR "Improper -a option.\nThe `$argument' domain argument is not allowed.\n";
		    }
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		if ($argument =~ /:/) {
		    ($net, $subnetmask) = split(/:/, $argument);
		    $cidr_size = undef;
		} elsif ($argument =~ /\//) {
		    ($net, $cidr_size) = split(/\//, $argument);
		    $subnetmask = undef;
		} else {
		    $net = $argument;
		    $subnetmask = $Defsubnetmask;
		    $cidr_size = undef;
		}
		$error = &CHECK_NET(\$net, \$cidr_size, \$subnetmask);
		if ($error) {
		    print STDERR "Improper $option option ($option $argument):\n$error";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		} elsif ($cidr_size < 8) {
		    print STDERR "Improper $option option ($option $argument).\nOnly network sizes /8 to /32 are supported.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		# Peek ahead and gather any `mode=' or `domain=' and/or
		# `ptr-owner=' argument(s) that may be present after first
		# setting the appropriate default values in case the argument(s)
		# are absent.
		#
		$supernet = $rfc2317_domain = $ptr_template = "";
		while ($#args > $i &&
		       (($args[$i + 1] =~ /^mode=/i && !$supernet) ||
			($args[$i + 1] =~ /^domain=/i && !$rfc2317_domain) ||
			($args[$i + 1] =~ /^ptr-owner=/i && !$ptr_template))) {
		    $arg2 = $args[++$i];
		    if ($arg2 =~ /^mode=/i) {
			$supernet = $arg2;
		    } elsif ($arg2 =~ /^domain=/i) {
			$rfc2317_domain = $arg2;
		    } else {
			$ptr_template = $arg2;
		    }
		}
		if ($supernet) {
		    unless ($supernet =~ /^mode=S$/i) {
			print STDERR "Improper `$supernet' argument in $option option.\nThe component of a valid `mode=' value must be `S'.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } elsif ($cidr_size >= 25) {
			print STDERR "Improper $option option ($option $argument).\nThe `$supernet' argument is for CIDR sizes 8 to 24; ignored.\n";
			$message_count++;
			$supernet = "";
		    } else {
			$supernet = "S ";
		    }
		} else {
		    $supernet = "S " if ($default_supernetting
					 && $cidr_size <= 24);
		}
		$Supernetting_enabled = 1 if $supernet;
		$domain_arg = $rfc2317_domain;
		$rfc2317_domain =~ s/^domain=//i;
		if ($rfc2317_domain) {
		    #
		    # Check to see if the domain name is not one that
		    # fits the pattern of a standard delegation in the
		    # `arpa.' zone, i.e., that of a /8, /16, or /24
		    # network.  First, however, clean up the domain
		    # name as follows:
		    #
		    #   1. Remove any redundant escape characters and
		    #      any trailing root zone (".") characters.
		    #   2. Remove any escapes that may have been
		    #      necessary to get the special characters
		    #      "<|>&()$?;'`" past the shell.
		    #   3. Escape any unescaped "@" character to be
		    #      consistent with BIND when it encounters
		    #      "@" as a literal character in a domain name.
		    #
		    1 while $rfc2317_domain =~ s/\\\\/\\/g;
		    $rfc2317_domain =~ s/([^\\])\.+$/$1/;
		    $rfc2317_domain =~ s/\\([<|>&\(\)\?;'`])/$1/g;
		    $rfc2317_domain =~ s/([^\\])@/$1\\@/g;
		    $error = 0;
		    if (($option eq "-n" && $cidr_size <= 24)
			|| $option eq "-a") {
			print STDERR "Improper -n option (-n $argument).\nThe `$domain_arg' domain argument is for CIDR sizes 25 to 32; ignored.\n" if $option eq "-n";
			print STDERR "Improper -a option (-a $argument).\nThe `$domain_arg' domain argument is meaningless; ignored.\n" if $option eq "-a";
			$message_count++;
			$rfc2317_domain = "";
		    } elsif ($rfc2317_domain && &CHECK_NAME($rfc2317_domain, 'SOA')) {
			print STDERR "Improper `domain=' argument (-n $argument  $domain_arg).\nIt is not a valid domain name.\n";
			$error = 1;
		    } elsif ($rfc2317_domain =~ /^((\d+[.]){1,3})?in-addr.arpa$/i) {
			print STDERR "Improper `domain=' argument (-n $argument  $domain_arg).\nIt conflicts with the hierarchy of parent networks under the ARPA zone.\n";
			$error = 1;
		    } elsif ($cidr_size < 32 &&
			     $rfc2317_domain =~ /^(\d+[.]){4}in-addr.arpa$/i) {
			print STDERR "Improper `domain=' argument (-n $argument  $domain_arg).\nIt conflicts with the delegation of a /32 network under the ARPA zone.\n";
			$error = 1;
		    } elsif ($cidr_size == 32 &&
			     $rfc2317_domain =~ /^(\d+[.]){4}in-addr.arpa$/i &&
			     lc($rfc2317_domain) ne &REVERSE($net) . ".in-addr.arpa") {
			print STDERR "Improper `domain=' argument (-n $argument  $domain_arg).\nIt represents a different /32 network under the ARPA zone.\n";
			$error = 1;
		    } elsif ($rfc2317_domain !~ /((\d+[.]){3})in-addr.arpa$/i &&
			     !$Domain) {
			#
			# The forward-mapping domain must be known in advance
			# so that references to it by the `domain=' argument
			# can be recognized and correctly processed.
			#
			print STDERR "Improper `domain=' argument (-n $argument  $domain_arg).\nIt cannot be properly processed without first specifying the -d option.\n";
			$error = 1;
		    }
		    if ($error) {
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		}
		$ptr_arg = $ptr_template;
		$ptr_template =~ s/^ptr-owner=//i;
		if ($ptr_template) {
		    #
		    # Check the PTR template for basic syntax and pay
		    # special attention to contexts which require or
		    # disallow a top-of-zone PTR record, e.g.,
		    #
		    #   $ORIGIN d.c.b.a.in-addr.arpa.
		    #   @  IN SOA  . . ( serial refresh retry expiry minimum )
		    #      IN NS   nameserver1.
		    #      IN NS   nameserver2.
		    #      IN PTR  some.domain.name.
		    #
		    # First, however, clean up the PTR owner name template
		    # as follows:
		    #
		    #   1. Remove any redundant escape characters.
		    #   2. Make sure to detect an empty pair of single
		    #      or double quotes (escaped or unescaped) and
		    #      change them to a pair of unescaped single
		    #      quote characters.  This is the syntax for
		    #      requesting a top-of-zone PTR record.
		    # NOTE: Since the literal "$" and "'" characters
		    #       are valid in the owner name field of a PTR
		    #       record, the cleaned-up copy of "$ptr_template"
		    #       is sufficient for basic DNS domain name testing
		    #       with the CHECK_NAME subroutine.
		    #
		    1 while $ptr_template =~ s/\\\\/\\/g;
		    $ptr_template = "''" if $ptr_template =~ /^([\\]?\'){2}$/ ||
					    $ptr_template =~ /^([\\]?\"){2}$/;
		    $error = 0;
		    if (($option eq "-n" && $cidr_size <= 24)
			|| $option eq "-a") {
			print STDERR "Improper -n option (-n $argument).\nThe `$ptr_arg' domain argument is for CIDR sizes 25 to 32; ignored.\n" if $option eq "-n";
			print STDERR "Improper -a option (-a $argument).\nThe `$ptr_arg' domain argument is meaningless; ignored.\n" if $option eq "-a";
			$message_count++;
			$ptr_template = "";
		    } elsif (&CHECK_NAME($ptr_template, 'PTR')) {
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nIt would create an invalid DNS domain name.\n";
			if ($ptr_template =~ /\.$/) {
			    print STDERR "The `ptr-owner' argument must be specified in domain-relative format.\n";
			}
			$error = 1;
		    } elsif ($ptr_template =~ /(^|[^\\])\$[^1-4]/) {
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nValid octet substitution tokens are \$1, \$2, \$3, and \$4.\n";
			$error = 1;
		    } elsif ($cidr_size != 32 && $ptr_template eq "''") {
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nA top-of-zone PTR record is only valid for a /32 network.\n";
			$error = 1;
		    } elsif ($cidr_size == 32 && $ptr_template ne "''" &&
			     (!$rfc2317_domain ||
			      $rfc2317_domain =~ /^(\d+[.]){4}in-addr.arpa$/i)) {
			$tmp1 = ($rfc2317_domain) ? "specified" : "default";
			$tmp2 = &REVERSE($net) . ".in-addr.arpa";
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nThe $tmp1 domain ($tmp2) for this network requires a\ntop-of-zone PTR record, i.e., omit the `ptr-owner=' argument or set it to \"''\".\n";
			$error = 1;
		    } elsif ($ptr_template !~ /(^''$|(^|[^\\])\$4)/) {
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nThe `\$4' token (representing the rightmost octet of an IP address) must always\nbe present for creating the owner names of non-top-of-zone PTR records.\n";
			$error = 1;
		    } elsif ($ptr_template =~ /^\*\./) {
			if ($cidr_size == 32) {
			    $error = "wildcard PTR record";
			} else {
			    $error = "set of wildcard PTR records";
			}
			print STDERR "Improper `ptr-owner=' argument (-n $argument  $ptr_arg).\nIt would create an ambiguous $error.\n";
			$error = 1;
		    }
		    if ($error) {
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } elsif ($ptr_template) {
			#
			# Do a character-by-character scan of the template
			# while looking backwards and forwards for IP address
			# octet tokens, non-printing characters, and whether
			# the current context is escaped or not.
			# The end product will be used as the replacement text
			# in a `s///' substitution operator for mapping an IP
			# address into its appropriate PTR record.  For example,
			# given an IP address of 156.153.254.81, say that we
			# want to construct the PTR record for the corresponding
			# class-B zone (153.156.in-addr.arpa).  The relative
			# owner name of the record needs to be `81.254'.
			# The Perl statements for doing this are:
			#
			#  $addr = "156.153.254.81";
			#  $addr =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$4.$3/;
			#  $addr equals "81.254".
			#
			# This does *not* work, however:
			#
			#  $addr = "156.153.254.81";
			#  $template = "\$4.\$3";
			#  $addr =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$template/;
			#  $addr equals "$4.$3".
			#
			# The contents of "$template" were literally substituted
			# for the matching pattern of "$addr".  The trick is to
			# use the `eval' modifier (/e) to evaluate the contents
			# of "$template" as an expression first.  The results
			# are then used as the substitution's replacement text.
			# Actually, it takes two `eval' modifiers (/ee) to get
			# the job done.  The first `eval' interpolates the
			# contents of "$template" and identifies the context of
			# "$3" and "$4" as capture variables.  In other words,
			# the replacement text is translated and staged.  The
			# second `eval' substitutes the captured text in "$3"
			# and "$4" and evaluates the resulting replacement
			# string in its entirety as a Perl expression.
			#
			#  $addr = "156.153.254.81";
			#  $template = "\$4.\$3";
			#  $addr =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$template/ee;
			#  $addr equals "81254".
			#  
			# What happened here was that "81" was substituted into
			# "$4" and "254" was substituted into "$3".  The "."
			# character, however, was evaluated as a concatenation
			# operator and thus the results were squashed together.
			# If the "$template" variable contained this instead:
			#
			#  $4 . '.' . $3
			#
			# the proper results would have been achieved (81.254).
			#
			# The following scan of "$ptr_template" will insert the
			# necessary quotes and concatenation operators to
			# achieve the desired results.
			# NOTE 1: Single quotes are used to surround non-token
			#         text to avoid the accidental interpolation
			#         of an `h2n' symbol name which may
			#         coincidentally exist.
			# NOTE 2: If "$ptr_template" contains the special value
			#         of "''", a short-circuit mechanism prevents
			#         it from being erroneously processed.
			#
			if ($ptr_template =~ /^\$\d/) {
			    $formatted_template = "";
			    $open_quote = 0;
			} else {
			    $formatted_template = "'";
			    $open_quote = 1;
			}
			$last_char = "";
			$ptr_template = "" if $ptr_template eq "''";
			while (length($ptr_template)) {
			    if ($ptr_template =~ /^\$(\d)/ && $last_char ne "\\") {
				$octet_token = $1;
				$formatted_template .= "'." if $open_quote;
				$formatted_template .= "\$$octet_token";
				$last_char = $octet_token;
				$ptr_template =~ s/^..//;
				$open_quote = 0;
			    } else {
				unless ($open_quote) {
				    #
				    # There are one or more characters still
				    # left in the template after the $[1-4]
				    # octet token that was just processed in
				    # the previous iteration.
				    #
				    $formatted_template .= ".'";
				    $last_char = "";
				    $open_quote = 1;
				}
				($char, $ptr_template) = split(//, $ptr_template, 2);
				if (ord($char) <= 32) {
				    #
				    # Create the escaped three-digit decimal
				    # representation of the non-printing ASCII
				    # character that's compatible with BIND.
				    #
				    $char = "0" . ord($char);
				    $char = "\\$char" if $last_char ne "\\";
				} elsif ($char =~ /^(@|\"|\')$/ && $last_char ne "\\") {
				    $char = "\\$char";
				}
				$formatted_template .= $char;
				($last_char = $char) =~ s/.*(.)$/$1/;
			    }
			}
			$formatted_template .= "'" if $open_quote;
			$ptr_template = $formatted_template;
		    }
		}
		# Now that the arguments have been vetted for proper syntax,
		# it's time to analyze the address space specifications to
		# see if there are any conflicts.
		#
		$overlap = "";
		$already_warned = $error = 0;
		foreach $subnet (&SUBNETS($net, $cidr_size)) {
		    ($subnet_key, $IP_range) = split(/:/, $subnet, 2);
		    #
		    # For networks with a CIDR size <= 24, each constituent
		    # class-A, class-B, or class-C subnet serves as an index
		    # key into the %Net_ranges hash.  The hash value is either
		    # a file-descriptor typeglob for a `-n' option or the null
		    # string for `-a', i.e.,
		    #
		    #  -n: $Net_ranges{"N"}     = "*main::DB.N"     (/8)
		    #  -a: $Net_ranges{"N"}     = ""                (/8)
		    #  -n: $Net_ranges{"N.N"}   = "*main::DB.N.N"   (/9  to /16)
		    #  -a: $Net_ranges{"N.N"}   = ""                (/9  to /16)
		    #  -n: $Net_ranges{"N.N.N"} = "*main::DB.N.N.N" (/17 to /24)
		    #  -a: $Net_ranges{"N.N.N"} = "";		    (/17 to /24)
		    #
		    # For networks with a CIDR size >= 25, the class-C supernet
		    # plus an appended ".X" string serves as the hash key index
		    # with a space-separated list of non-overlapping IP address
		    # ranges as the hash value.  The distinction between a `-n'
		    # or `-a' network is made by concatenating each IP-range to
		    # the class-C supernet to create the unique key, i.e.,
		    #
		    #  -n/-a: $Net_ranges{"N.N.N.X"} = "IP-range IP-range ..."
		    #     -n: $Net_ranges{"N.N.N.IP-range"} = "*main::DB.zone"
		    #     -a: $Net_ranges{"N.N.N.IP-range"} = "";
		    #
		    # NOTE: By default, each non-overlapping sub-class-C
		    #       network within a class-C supernet is assigned
		    #       to a unique domain name.  However, since the
		    #       last octet of each such network address is
		    #       unique within the supernet (due to `$4' being
		    #       present in the PTR template), there's nothing
		    #       to prohibit these sub-class-C networks from
		    #       being combined into one "super-domain", i.e.,
		    #       sharing the same "domain=" argument.
		    #
		    #####
		    #
		    # The first thing we need to do is to check for the
		    # following network overlap conditions:
		    #
		    #   1.  Any overlap between networks of a different
		    #       class (A, B, C, or sub-C) will be reported
		    #       as a fatal error unless supernetting is in
		    #       effect (+S global option or `mode=S' argument
		    #       to -n/-a option) when the larger network
		    #       is declared with -n/-a.
		    #
		    #   2.  Overlaps between networks of the same class
		    #       (A, B, or C) will be let off with a warning.
		    #       Overlaps between sub-class-C networks will
		    #       be reported as a fatal error, however.
		    #
		    #   3.  Intra-class A, B, or C overlaps (#2 above)
		    #       will be reported as a fatal error if the
		    #       specified option (-n/-a) is inconsistent.
		    #
		    # In conjunction with the %Net_ranges hash described
		    # previously, the %allocated_octets hash will keep
		    # track of networks per the following example:
		    #
		    #   -a 192.168.15/24
		    #   $Net_ranges{"192.168.15"} = "";
		    #   $tmp = "192.168.15 -a 192.168.15/24";
		    #   $allocated_octets{"192"}        = $tmp;
		    #   $allocated_octets{"192.168"}    = $tmp;
		    #   $allocated_octets{"192.168.15"} = $tmp;
		    #
		    # Subsequent attempts to process the following options:
		    #
		    #   -a 192/8
		    #   -a 192.168/16
		    #
		    # will fail because the octets would already be registered
		    # as keys in the %allocated_octets hash with a hash value
		    # pointing to a class-C network (192.168.15) as the
		    # registrant.  The remaining fields of the hash value
		    # ("-a 192.168.15/24") reference the original option for
		    # generating a descriptive error message.
		    #
		    # However, if supernetting is specified globally with the
		    # +S option or selectively by adding the `mode=S' argument
		    # to a -n/-a option, e.g.,
		    #
		    #   -a 192/8 mode=S
		    #   +S
		    #   -a 192.168/16
		    #
		    # then no error will be generated when a network overlaps
		    # with another of a larger class that has been declared to
		    # be a supernet.
		    #
		    if ($cidr_size == 8) {
			if (exists($allocated_octets{$subnet_key})) {
			    #
			    # Either a duplicate class A network exists or
			    # there's an overlap with an existing class B
			    # and/or class C network.
			    #
			    $overlap = $allocated_octets{$subnet_key};
			    if ($overlap !~ /^$subnet_key /) {
				#
				# An overlapping class B and/or class C and/or
				# one or more sub-class-C network(s) exist(s).
				#
				if ($supernet) {
				    $overlap = "";
				} else {
				    $error = 1;
				}
			    } elsif ($overlap !~ /\S+ $option /) {
				#
				# The duplicate class A network specification
				# is ambiguous because one is a -a option and
				# another is a -n option.
				# 
				$error = 3;
			    }
			}
			unless ($overlap) {
			    $Net_ranges{$subnet_key} = "";
			    $allocated_octets{$subnet_key} =
				"$subnet_key $supernet$option $argument";
			}
		    } elsif ($cidr_size <= 16) {
			($octet1 = $subnet_key) =~ s/\..+//;
			if (exists($allocated_octets{$subnet_key})) {
			    $overlap = $allocated_octets{$subnet_key};
			    if ($overlap !~ /^$subnet_key /) {
				#
				# An overlapping class C or one or more
				# sub-class-C network(s) exist(s).
				#
				if ($supernet) {
				    $overlap = "";
				} else {
				    $error = 1;
				}
			    } elsif ($overlap !~ /\S+ $option /) {
				#
				# The duplicate class B network specification
				# is ambiguous because one is a -a option and
				# another is a -n option.
				# 
				$error = 3;
			    }
			} elsif (exists($allocated_octets{$octet1})) {
			    $tmp = $allocated_octets{$octet1};
			    if ($tmp =~ /^\d+ /) {
				#
				# An overlapping class A network exists.
				# 
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class A network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			}
			unless ($overlap) {
			    $Net_ranges{$subnet_key} = "";
			    $tmp = "$subnet_key $supernet$option $argument";
			    $allocated_octets{$subnet_key} = $tmp;
			    unless (exists($allocated_octets{$octet1})) {
				#
				# The first octet has not been allocated by a
				# previously-specified non-overlapping network.
				#
				$allocated_octets{$octet1} = $tmp;
			    }
			}
		    } elsif ($cidr_size <= 24) {
			$octet1 = $octets1_and_2 = $subnet_key;
			$octet1 =~ s/\..+//;
			$octets1_and_2 =~ s/\.\d+$//;
			if (exists($allocated_octets{$subnet_key})) {
			    $overlap = $allocated_octets{$subnet_key};
			    if (exists($Net_ranges{"$subnet_key.X"})) {
				#
				# One or more overlapping sub-class-C
				# network(s) exist(s).
				#
				if ($supernet) {
				    $overlap = "";
				} else {
				    $error = 1;
				}
			    } elsif ($overlap !~ /\S+ $option /) {
				#
				# The duplicate class C network specification
				# is ambiguous because one is a -a option and
				# another is a -n option.
				# 
				$error = 3;
			    }
			} elsif (exists($allocated_octets{$octets1_and_2})) {
			    $tmp = $allocated_octets{$octets1_and_2};
			    if ($tmp =~ /^\d+\.\d+ /) {
				#
				# An overlapping class B network exists.
				# 
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class B network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			} elsif (exists($allocated_octets{$octet1})) {
			    $tmp = $allocated_octets{$octet1};
			    if ($tmp =~ /^\d+ /) {
				#
				# An overlapping class A network exists.
				# 
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class A network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			}
			unless ($overlap) {
			    $Net_ranges{$subnet_key} = "";
			    $tmp = "$subnet_key $supernet$option $argument";
			    $allocated_octets{$subnet_key} = $tmp;
			    #
			    # Allocate octets 1 and 2 to this class-C network
			    # unless already allocated by a previously specified
			    # non-overlapping network.
			    #
			    unless (exists($allocated_octets{$octets1_and_2})) {
				$allocated_octets{$octets1_and_2} = $tmp;
			    }
			    unless (exists($allocated_octets{$octet1})) {
				$allocated_octets{$octet1} = $tmp;
			    }
			}
		    } else {
			#
			# Deal with sub-class-C networks (/25 to /32).
			# First, see if there is an overlap conflict with
			# some other class A, B, or C network.
			#
			$octet1 = $octets1_and_2 = $subnet_key;
			$octet1 =~ s/\..+//;
			$octets1_and_2 =~ s/\.\d+$//;
			if (exists($allocated_octets{$subnet_key})) {
			    if (exists($Net_ranges{$subnet_key})) {
				#
				# An overlapping class-C network exists.
				#
				$tmp = $allocated_octets{$subnet_key};
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class C network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			} elsif (exists($allocated_octets{$octets1_and_2})) {
			    $tmp = $allocated_octets{$octets1_and_2};
			    if ($tmp =~ /^\d+\.\d+ /) {
				#
				# An overlapping class B network exists.
				# 
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class B network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			} elsif (exists($allocated_octets{$octet1})) {
			    $tmp = $allocated_octets{$octet1};
			    if ($tmp =~ /^\d+ /) {
				#
				# An overlapping class A network exists.
				# 
				if ($tmp =~ /^\S+ S /) {
				    #
				    # The class A network was a supernet.
				    #
				    $overlap = "";
				} else {
				    $overlap = $tmp;
				    $error = 1;
				}
			    }
			}
			unless ($overlap) {
			    #
			    # Now see if there are any sub-class-C overlaps
			    # within the parent class-C supernet.
			    # NOTE: Even though `h2n' will tolerate and
			    #       simply isue a warning when it detects
			    #       duplicate intra-class (A, B, and C only)
			    #       networks with identical -n or -a options,
			    #       no such indulgence will be made to see if
			    #       two such sub-class-C networks are exactly
			    #       duplicated because of the added variables
			    #       of the RFC-2317 zone name and the template
			    #       for PTR owner names.  Therefore, no IP
			    #       address overlap of any kind is permitted
			    #       for sub-class-C networks.
			    #
			    $classC_key = $sub_classC_key = $subnet_key;
			    unless (exists($Net_ranges{"$classC_key.X"})) {
				#
				# This is the first sub-class-C network in
				# the parent class-C supernet.
				#
				$Net_ranges{"$classC_key.X"} = $IP_range;
				$sub_classC_key .= ".$IP_range";
				$Net_ranges{$sub_classC_key} = "";
				$tmp = "$classC_key $option $argument  $domain_arg  $ptr_arg";
				$allocated_octets{$classC_key} = $tmp;
				#
				# Allocate octets 1 and 2 to this sub-class-C
				# network unless already allocated by a
				# previously specified non-overlapping network.
				#
				unless (exists($allocated_octets{$octets1_and_2})) {
				    $allocated_octets{$octets1_and_2} = $tmp;
				}
				unless (exists($allocated_octets{$octet1})) {
				    $allocated_octets{$octet1} = $tmp;
				}
				# Finally, register the sub-class-C hash key
				# so that a descriptive error message can be
				# generated in case a subsequent sub-class-C
				# network specification causes a conflict.
				#
				$allocated_octets{$sub_classC_key} = $tmp;
				
			    } else {
				#
				# One or more sub-class-C networks already
				# exist in the parent class-C supernet.
				# The only valid situation at this point
				# is if we are appending a non-overlapping
				# IP address range to an existing list of
				# non-overlapping sub-class-C address ranges.
				#
				($first_IP, $last_IP) = split(/-/, $IP_range, 2);
				$last_IP = $first_IP unless defined($last_IP);
				foreach $tmp (split(' ', $Net_ranges{"$classC_key.X"})) {
				    ($tmp1, $tmp2) = split(/-/, $tmp, 2);
				    $tmp2 = $tmp1 unless defined($tmp2);
				    if (($first_IP >= $tmp1 && $first_IP <= $tmp2)
					|| ($tmp1 >= $first_IP && $tmp1 <= $last_IP)) {
					#
					# An overlap exists.  Reconstruct
					# the sub-class-C index key into
					# the %allocated_octets hash so
					# that a descriptive error message
					# can be generated.
					#
					$overlap = $allocated_octets{"$classC_key.$tmp"};
					$error = 2;
					last;
				    }
				}
				unless ($overlap) {
				    #
				    # Append the non-overlapping range of
				    # addresses to the existing list and
				    # initialize the new hash key associated
				    # with this sub-class-C network.
				    #
				    $Net_ranges{"$classC_key.X"} .= " $IP_range";
				    $sub_classC_key .= ".$IP_range";
				    $Net_ranges{$sub_classC_key} = "";
				    #
				    # There is no need to register the parent
				    # class-C supernet in the %allocated_octets
				    # hash since this has already been done by
				    # the supernet's first sub-class-C network.
				    # However, we do want to register the
				    # current sub-class-C network so that
				    # any future conflict can be accompanied
				    # by a descriptive error message.
				    #
				    $allocated_octets{$sub_classC_key} =
				        "$classC_key $option $argument  $domain_arg  $ptr_arg";
				}
			    }
			}
		    }
		    if ($overlap) {
			$overlap =~ s/^\S+ (S )?//;
			unless ($error) {
			    if ($verbose && !$already_warned) {
				print STDERR "Warning: overlapping $option option ($option $argument).\n";
				print STDERR "It redundantly includes one or more (sub)networks from a previous option:\n  $overlap\nCheck your -n/-a networks and subnetmasks/CIDRsizes for overlap.\n";
				$already_warned = 1;
				$overlap = "";
				$message_count++;
			    }
			} elsif ($error == 1) {
			    print STDERR "Improper $option option ($option $argument).\n";
			    print STDERR "It overlaps with a network of a different class from a previous option:\n  $overlap\nThey can't simultaneously specify a part of the same DNS address-to-name space.\n";
			} elsif ($error == 2) {
			    print STDERR "Improper $option option ($option $argument).\n";
			    print STDERR "It overlaps with another sub-class-C network from a previous option:\n  $overlap\nTo prevent DNS naming ambiguities, sub-class-C overlaps are always disallowed.\n";
			} else {
			    print STDERR "Improper $option option ($option $argument).\n";
			    print STDERR "It conflicts with a previous option ($overlap).\nA network specification cannot be shared between the -n and -a options.\n";
			}
			if ($error) {
			    &GIVE_UP unless defined wantarray;
			    $message_count++;
			    $i++;
			    last;
			}
		    }
		    if ($subnet_key eq "127.0.0") {
			if ($option eq "-a") {
			    #
			    # Skip the automatic generation of the generic
			    # reverse-mapping db file for the loopback network.
			    #
			    $MakeLoopbackSOA = 0;
			} else {
			    #
			    # Skip the automatic declaration of the loopback
			    # network in the various configuration files
			    # generated by the GEN_BOOT() subroutine.
			    #
			    $MakeLoopbackZone = 0;
			}
		    }
		    next if $option eq "-a";

		    # Finish processing the -n option by creating the necessary
		    # data structures to allocate the "db.*" files to which the
		    # PTR records will get written.
		    #
		    if ($cidr_size <= 24) {
			if ($cidr_size == 8) {
			    #
			    # PTR records for a /8 network will get
			    # written to a single class-A zone file.
			    #
			    $ptr_template = "\$4.'.'.\$3.'.'.\$2";
			} elsif ($cidr_size <= 16) {
			    #
			    # PTR records for networks sizes /9 to /16 will get
			    # get written to the equivalent number of class-B
			    # zone files.
			    #
			    $ptr_template = "\$4.'.'.\$3";
			} else {
			    #
			    # PTR records for networks sizes /17 to /24 will get
			    # get written to the equivalent number of class-C
			    # zone files.
			    #
			    $ptr_template = "\$4";
			}
			$zone_file = $subnet_key;
			$zone_name = &REVERSE($zone_file) . ".in-addr.arpa";
		    } else {
			#
			# Assemble the sub-class-C network information.
			#
			unless ($ptr_template) {
			    #
			    # No `ptr-owner=' argument was specified.
			    # Set the template to the appropriate default
			    # value based on the sub-class-C network's size.
			    #
			    if ($cidr_size == 32 &&
				(!$rfc2317_domain ||
				 $rfc2317_domain =~ /^(\d+[.]){4}in-addr.arpa$/i)) {
				#
				# Accommodate the special case of the
				# PTR record being at the zone top
				# along with the SOA and NS records.
				#
				$ptr_template = "''";
				$ptr_arg = "[ptr-owner='']";
			    } else {
				$ptr_template = "\$4";
				$ptr_arg = "[ptr-owner=\$4]";
			    }
			}
			if ($rfc2317_domain) {
			    $zone_name = $rfc2317_domain;
			} else {
			    $zone_file = $sub_classC_key;
			    $zone_name = &REVERSE($zone_file) . ".in-addr.arpa";
			    $domain_arg = "[domain=$zone_name]";
			}
			$lc_zone_name = lc($zone_name);
			#
			# It is possible for multiple sub-class-C networks
			# belonging to different class-C supernets to specify
			# (explicitly or implicitly) the same "domain=" argument
			# in their respective `-n' options.
			# However, the default PTR template of `$4' is
			# insufficient in these cases because the last octet
			# that it represents is not unique across different
			# class-C supernets.
			# Choosing such a shared reverse-mapping zone requires
			# that the "ptr-owner=" argument be specified (either
			# explicitly or implicitly) so that the IP addresses of
			# each sub-class-C network map to unique PTR owner names
			# in the common zone file.
			#
			$ptr_map = "$classC_key.xxx";
			$ptr_map =~ s/(\d+)\.(\d+)\.(\d+)\.(\S+)/$ptr_template/ee;
			$ptr_map = lc($ptr_map);
			if (exists($sub_classC{$lc_zone_name}{first_supernet})
			    && $sub_classC{$lc_zone_name}{first_supernet} ne $classC_key
			    && exists($sub_classC{$lc_zone_name}{$ptr_map})) {
			    $error = $sub_classC{$lc_zone_name}{$ptr_map};
			    print STDERR "The following -n option is ambiguously specified:\n  -n $argument  $domain_arg  $ptr_arg\n";
			    print STDERR "There is the possibility of overlapping PTR owner names with a prior -n option:\n  $error\n";
			    print STDERR "Sub-class-C networks belonging to different class-C supernets must specify\nuniquely-mapping `ptr-owner=' arguments if they share a common domain name.\n";
			    &GIVE_UP unless defined wantarray;
			    $message_count++;
			    last;
			}
			$sub_classC{$lc_zone_name}{$ptr_map} = "$argument  $domain_arg  $ptr_arg";
			if ($rfc2317_domain) {
			    if (lc($rfc2317_domain) eq lc($Domain)) {
				#
				# The PTR records are to be written to the
				# same zone file as the forward-mapping
				# domain data (-d option).
				#
				$net_file = "DOMAIN";
				$Net_ranges{$sub_classC_key} =
				    "\*" . qualify($net_file) .
				    " "  . $ptr_template;
				$sub_classC{lc($Domain)}{first_supernet} = $classC_key unless exists($sub_classC{lc($Domain)}{first_supernet});
				next;
			    }
			    $zone_file = $rfc2317_domain;
			    if ($rfc2317_domain =~ /\.in-addr\.arpa$/i) {
				$zone_file = &REVERSE($rfc2317_domain);
				$zone_file =~ s/^arpa\.in-addr\.//i;
				$zone_file =~ s/\.($cidr_size)\/(.+)$/.$2\/$1/;
			    }
			    #
			    # Certain non-alphanumeric characters cause
			    # trouble when they appear in filenames but
			    # are nonetheless valid in a DNS domain name.
			    # Translate these nettlesome characters into
			    # a harmless "%" character after making sure
			    # to unescape any "$@()" characters.  Escaped
			    # whitespace will be converted to underscore
			    # characters and then any remaining escapes
			    # will be eliminated.
			    #
			    for ($zone_file) {
				s/\\([\$@\(\)])/$1/g;
				s/[\/<|>&\[\(\)\$\?;'`]/%/g;
				s/\\\s/_/g;
				s/\\//g;
			    }
			}
		    }
		    $net_file = "DB.$zone_file";
		    #
		    # Prepend our package name to the value that is stored
		    # in the "%Net_ranges" hash.  This hash value is used
		    # as a globally-scoped file descriptor typeglob for
		    # printing RRs to the DB files and as a key into
		    # the "%DB_filehandle" hash.
		    #
		    $subnet_key = $sub_classC_key if $cidr_size >= 25;
		    $Net_ranges{$subnet_key} = "\*" . qualify($net_file) .
					       " "  . $ptr_template;
		    next if ($cidr_size >= 25 &&
			     exists($sub_classC{$lc_zone_name}{first_supernet}));

		    # Make sure the SOA record gets created as well as
		    # adding the necessary entries to the boot/conf file(s).
		    #
		    push(@makesoa, "db.$zone_file $net_file");
		    push(@bootmsgs, "$zone_name db.$zone_file");
		    $Net_zones{$subnet_key} = "$zone_name $zone_file";
		    $sub_classC{$lc_zone_name}{first_supernet} = $classC_key if $cidr_size >= 25;
		    $lastNorD .= " $zone_file";

		}   # end of "foreach $subnet" loop
	    }	    # end of "while -n/-a" loop 
	    $i--;

	} elsif ($option eq "-B") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -B option; the required boot file pathname argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $Bwd = $args[++$i] . "/";
	    $Bwd =~ s/\/+$/\//;
	    unless ($Bwd =~ /^\//) {
		print STDERR "Must use an absolute pathname for -B option.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }

	} elsif ($option eq "-b") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -b option; the `boot' file argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $Bootfile = $args[++$i];

	} elsif ($option eq "-C") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -C option; must specify the name of a comment file as an argument.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $Commentfile = $args[++$i];
	    if (! -r $Commentfile || -z $Commentfile) {
		print STDERR "Invalid file `$Commentfile' specified for -C option.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }

	} elsif ($option eq "-c") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -c option; required domain name argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^mode=/i) {
		    print STDERR "Improper -c option; a domain name must precede the `$argument' argument.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		($tmp = $argument) =~ s/\.+$//;
		if (length($tmp) == 0 || &CHECK_NAME($tmp, 'SOA')) {
		    print STDERR "Improper -c option; the domain name `$argument' is invalid.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		unless ($tmp =~ /\./) {
		    unless ($Domain) {
			print STDERR "Improper -c option; the single-label domain name of `$argument'\nrequires that the -d option be first specified so that the\ndefault fully-qualified domain can be appended.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			last;
		    } else {
			$argument = "$tmp.$Domain";
		    }
		} else {
		    $argument = $tmp;
		}
		($tmp = lc($argument)) =~ s/\./\\./g; 
		if (exists($cpatrel{$tmp})) {
		    print STDERR "Improper -c option; the domain `$args[$i]' has\nalready been specified in a prior -c option.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		$cpatrel{$tmp} = $argument;
		push(@cpats, $tmp);
		$cModeSpec{$tmp} = "";		# a `mode=' arg. may update this

		# Peek ahead and see if there's an optional `mode=' argument.
		#
		if ($#args > $i && $args[$i + 1] =~ /^mode=/i) {
		    ($argument = uc($args[++$i])) =~ s/^MODE=//;
		    $argument =~ s/[,]//g;
		    $error = 0;
		    unless ($argument =~ /^[ADIQ]+$/) {
			print STDERR "Improper `$argument' argument in -c option.\nComponents of a valid `mode=' value are: A, D, I, and Q.\n";
			$error = 1;
		    } elsif ($argument =~ /^[AI]*Q$/) {
			print STDERR "Improper `$argument' argument in -c option.\n'mode=' component of \"Q\" requires that \"D\" also be specified.\n";
			$error = 1;
		    } elsif ($argument =~ /I/ && !$Domain) {
			print STDERR "Improper `$argument' argument in -c option.\n'mode=' component of \"I\" requires that the -d option be first specified.\n";
			$error = 1;
		    } elsif ($argument =~ /I/ && $cpatrel{$tmp} !~ /$Domainpattern$/i) {
			print STDERR "Improper `-c $cpatrel{$tmp}' option.\nThe `mode=' component of \"I\" requires the -c domain to be\nan intra-zone subdomain of the -d option ($Domain).\n";
			$error = 1;
		    }
		    if ($error) {
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $cModeSpec{$tmp} = $argument; # update with actual flag(s)
		}
	    }
	    $i--;

	} elsif ($option eq "+C") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper +C option; the `conf' prepend file argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    if ($Conf_prefile) {
		if ($verbose) {
		    print STDERR "Extra +C option ($argument) ignored, only one instance allowed.\n";
		    $message_count++;
		}
		$i++;
		next;
	    }
	    $error = 0;
	    if (!-e $argument) {
		print STDERR "Improper +C option; `$argument' is a non-existent file.\n";
		$error = 1;
	    } elsif (!-f $argument) {
		print STDERR "Improper +C option; `$argument' is not a plain file.\n";
		$error = 1;
	    } elsif (!-r $argument) {
		print STDERR "Improper +C option; `$argument' is a non-readable file.\n";
		$error = 1;
	    }
	    if ($error) {
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		last;
	    } else {
		($Conf_prefile = $argument) =~ s/^\.\///;
		$Conf_prefile = "$Pwd/$Conf_prefile" if $Conf_prefile !~ /^\//;
	    }

	} elsif ($option eq "+c") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper +c option; must specify a `conf' file and/or `mode=' argument(s)\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^mode=/i) {
		    ($argument = uc($argument)) =~ s/^MODE=//;
		    unless ($argument =~ /^[MS]$/) {
			print STDERR "Improper `$argument' argument in +c option.\nValid `mode=' value is either `S' or `M'.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $new_fmt_Conffile = ($argument eq "S") ? 0 : 1;
		} else {
		    $Conffile = $argument;
		}
	    }
	    $i--;

	} elsif ($option eq "-d") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -d option; at least one argument, the domain name, must be specified.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    if ($argument =~ /^(db|spcl|mode)=/i) {
		print STDERR "Improper -d option; a domain name must precede any `db=|spcl=|mode=' arguments.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    if ($Domain) {
		if ($verbose) {
		    print STDERR "Extra -d option ($argument) ignored, only one instance allowed.\n";
		    $message_count++;
		}
		$i++;
		while ($args[$i] =~ /^(db|spcl|mode)=/i) {
		    $i++;
		}
		next;
	    }
	    ($Domain = $argument) =~ s/\.+$//;
	    if (length($Domain) == 0 || &CHECK_NAME($Domain, 'SOA')) {
		print STDERR "Improper -d option; the domain name `$argument' is invalid.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    ($Domainpattern = ".$Domain") =~ s/\./\\./g;  # for stripping domain
	    ($Domainfile = $Domain) =~ s/\..*//;
	    $Specialfile = "spcl.$Domainfile";
	    $Domainfile  = "db.$Domainfile";

	    # Process any optional arguments that may be present.
	    #
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument !~ /^(db|spcl|mode)=/i) {
		    print STDERR "Improper -d option; unknown argument ($argument).\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		if (!defined($AltDb) && $argument =~ /^db=/i) {
		    ($Domainfile = $argument) =~ s/^db=//i;
		    if ($Domainfile =~ /\// && $Domainfile !~ /^\.\/[^\/]+/) {
			print STDERR "No pathname allowed in `-d db=' argument.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $AltDb = 1;
		} elsif (!defined($AltSpcl) && $argument =~ /^spcl=/i) {
		    ($Specialfile = $argument) =~ s/^spcl=//i;
		    $AltSpcl = 1;
		} elsif ($argument =~ /^mode=/i) {
		    if ($argument =~ /^mode=d/i) {
			if (!defined($UseDefaultDomain)) {
			    $UseDefaultDomain = 1;
			} elsif ($verbose) {
			    print STDERR "Extra -d argument ($argument) ignored, only one instance allowed.\n";
			    $message_count++;
			}
		    } else {
			print STDERR "Improper `$argument' argument in -d option.\nThe component of a valid `mode=' value must be `D'.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		} elsif ($verbose) {
		    print STDERR "Extra -d argument ($argument) ignored, only one instance allowed.\n";
		    $message_count++;
		}
	    }
	    $i--;

	    # Add entry to the boot file.
	    #
	    $lastNorD = "-d $Domainfile";
	    push(@makesoa, "$Domainfile DOMAIN");
	    push(@bootmsgs, "$Domain $Domainfile");

	} elsif ($option eq "-e") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -e option; required domain name argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		($tmp = lc($argument)) =~ s/\.+$//;
		if (length($tmp) == 0 || &CHECK_NAME($tmp, 'SOA')) {
		    print STDERR "Improper -e option; the domain name `$argument' is invalid.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		unless ($tmp =~ /\./) {
		    unless ($Domain) {
			print STDERR "Improper -e option; the single-label domain name of `$argument'\nrequires that the -d option be previously specified so that\nthe default fully-qualified domain can be appended.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } else {
			$argument = "$tmp.$Domain";
		    }
		} else {
		    $argument = $tmp;
		}
		if (exists($e_opt_domain{$argument})) {
		    print STDERR "Improper -e option; the domain `$args[$i]' has\nalready been specified in a prior -e option.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		$e_opt_domain{$argument} = 1;
		$argument =~ s/\./\\./g; 
		push(@elimpats, $argument);
	    }
	    $i--;

	} elsif ($option eq "-f") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -f option; the required filename argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $file = $args[++$i];
	    $tmp1 = join(":", (stat($file))[0..1]);
	    if ($tmp1 eq $rcfile) {
		print STDERR "Warning: -f option ignored; `$file' has been already\n";
		print STDERR "         processed as an $Program configuration file.\n";
		$message_count++;
		$i++;
		next;
	    }
	    unless (open(*OPT, $file)) {
		print STDERR "Unable to open options file `$file': $!\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    # Read through each line of the options file and split it into a
	    # stream of individual option/argument tokens via the following
	    # shell-like rules:
	    #
	    #   1. Quoting characters are the escape (\), the literal or
	    #      single quote ('), and the grouping or double quote (").
	    #   2. Tokens are delimited by unquoted whitespace (space, tab,
	    #      and newline).  Multi-line tokens are not allowed, however.
	    #      A quoted newline generates an "unexpected end of input"
	    #      error.
	    #   3. Tokens that begin with an unquoted "#" or ";" character
	    #      signify the start of a comment which then extends to
	    #      the rest of the line.
	    #   4. A pair of literal (single) quotes removes the special
	    #      meaning of all enclosed characters, i.e., nothing can be
	    #      escaped since "\" becomes a literal backslash character.
	    #      Use \' or "'" outside of single quotes to refer to the
	    #      literal ' character.
	    #   5. A pair of grouping (double) quotes removes the special
	    #      meaning of all enclosed characters except "\" and the
	    #      double quote itself.  This means that the backslash and
	    #      the double quote character can be rendered into their
	    #      literal meaning by preceding them with an escape.
	    #
	    @file_args = ();
	    $line_num = $skip_next_token = 0;
	    $error = "";
	    while (<OPT>) {
		$line_num++;
		chop;
		s/\s+$//;
		$original_line = $_;
		s/^\s+//;
		next if /^$/ || /^[#;]/;
		$comment = 0;
		unless (/[\\"']/) {
		    foreach $token (split(' ')) {
			if ($token =~ /^[#;]/) {
			    $comment = 1;
			    last;
			}
			if ($skip_next_token) {
			    $skip_next_token = 0;
			    next;
			}
			if ($token eq "-f") {
			    if ($verbose) {
				print STDERR "Already reading file `$file'; nested -f option ignored.\n";
				$message_count++;
			    }
			    $skip_next_token = 1;
			    next;
			}
			push(@file_args, $token);
		    }
		    next if $comment;
		} else {
		    #
		    # Assemble a token character-by-character until unquoted
		    # whitespace or the end of the line is reached.
		    #
		    $token = "";
		    $open_quote = 0;
		    while (length($_)) {
			($char, $_) = split(//, $_, 2);
			if ($open_quote == 1) {
			    if ($char eq "'") {
				$open_quote = 0;
				next;
			    }
			} elsif ($char eq "\\") {
			    unless (length($_)) {
				$error = "unexpected end of input\n> $original_line";
				last;
			    }
			    ($char, $_) = split(//, $_, 2);
			} elsif ($open_quote == 2) {
			    if ($char eq '"') {
				$open_quote = 0;
				next;
			    }
			} elsif ($char eq "'") {
			    $open_quote = 1;
			    next;
			} elsif ($char eq '"') {
			    $open_quote = 2;
			    next;
			} elsif ($char =~ /\s/) {
			    if ($token =~ /[#;]/) {
				$comment = 1;
				last;
			    } elsif ($skip_next_token) {
				$skip_next_token = 0;
			    } elsif ($token eq "-f") {
				if ($verbose) {
				    print STDERR "Already reading file `$file'; nested -f option ignored.\n";
				    $message_count++;
				}
				$skip_next_token = 1;
			    } else {
				push(@file_args, $token);
			    }
			    $token = "";
			    s/^\s+//;
			    next;
			}
			$token .= $char;
		    }
		    if ($open_quote) {
			$error = "unbalanced quotes\n> $original_line";
		    } elsif (!$comment && !$error) {
			if ($skip_next_token) {
			    $skip_next_token = 0;
			} elsif ($token eq "-f") {
			    if ($verbose) {
				print STDERR "Already reading file `$file'; nested -f option ignored.\n";
				$message_count++;
			    }
			    $skip_next_token = 1;
			} else {
			    push(@file_args, $token);
			}
		    }
		}
		last if $error;
	    }
	    close(*OPT);
	    if ($error) {
		print STDERR "Error in option file `$file' at line $line_num; $error\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } else {
		#
		# Preserve the order in which the arguments were presented to
		# this program by appending what's left of the original argument
		# list to those that were just read in from the options file.
		# The next argument to be processed will be the first one from
		# the just-read file.
		#
		push(@file_args, @args[++$i..$#args]);
		@args = @file_args;
		$i = -1;
	    }

	} elsif ($option eq "-H") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -H option; must specify the name of a host file as an argument.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $Hostfile = $args[++$i];
	    if (! -r $Hostfile || -z $Hostfile) {
		print STDERR "Invalid file `$Hostfile' specified for -H option.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }

	} elsif ($option eq "-h") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -h option; the zone master name server (SOA MNAME) is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $Host = $args[++$i];

	} elsif ($option eq "-I") {
	    #
	    # Maintain backward-compatibility in case there is no argument.
	    #
	    $check_level = "fail";
	    $rfc952 = 0;
	    $rfc1123 = 1;
	    $audit = 1;
	    $DefAction = "Skipping";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(no|false|off|none|disable|ignore)$/i) {
		    $check_level = "ignore";
		    $rfc952 = 0;
		    $rfc1123 = 0;
		    $audit = 0;
		    $DefAction = "Warning";
		} elsif ($argument =~ /^warn$/i) {
		    $check_level = "warn";
		    $rfc952 = 0;
		    $rfc1123 = 1;
		    $audit = 0;
		    $DefAction = "Warning";
		} elsif ($argument =~ /^audit\-only$/i) {
		    $check_level = "audit-only";
		    $rfc952 = 0;
		    $rfc1123 = 0;
		    $audit = 1;
		    $DefAction = "Warning";
		} elsif ($argument =~ /^audit$/i) {
		    $check_level = "audit";
		    $rfc952 = 0;
		    $rfc1123 = 1;
		    $audit = 1;
		    $DefAction = "Warning";
		} elsif ($argument =~ /^warn\-strict$/i) {
		    $check_level = "warn-strict";
		    $rfc952 = 1;
		    $rfc1123 = 1;
		    $audit = 1;
		    $DefAction = "Warning";
		} elsif ($argument =~ /^(yes|true|on|check|enable|fail)$/i) {
		    $check_level = "fail";
		    $rfc952 = 0;
		    $rfc1123 = 1;
		    $audit = 1;
		    $DefAction = "Skipping";
		} elsif ($argument =~ /^strict$/i) {
		    $check_level = "strict";
		    $rfc952 = 1;
		    $rfc1123 = 1;
		    $audit = 1;
		    $DefAction = "Skipping";
		} elsif ($argument =~ /^rfc-?2782$/i) {
		    $rfc2782 = 1;
		} else {
		    if ($verbose) {
			print STDERR "Unknown `-I' argument (`$argument'); name checking remains `$check_level'.\n";
			$message_count++;
		    }
		}
	    }
	    $i--;

	} elsif ($option eq "-i") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -i option; the SOA serial number is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    $error = 0;
	    if ($UseDateInSerial) {
		print STDERR "Improper -i option; it is incompatible with the already-specified -y option.\n";
		$error = 1;
	    } elsif ($argument !~ /^\d+$/ || $argument > 4294967295) {
		print STDERR "Improper -i option; the SOA serial number must be a non-negative\ninteger between zero and 4294967295.  See RFC-1982 for details.\n";
		$error = 1;
	    }
	    if ($error) {
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } else {
		$NewSerial = $argument;
		if ($NewSerial == 0) {
		    print STDERR "Warning: Setting the SOA serial number to zero may cause unpredictable results\n         with slave name servers.  See RFC-1982, Section 7 for details.\n";
		    $message_count++;
		}
	    }

	} elsif ($option eq "-L") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -L option; the max-filehandles argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    if ($argument !~ /^\d+$/ && $verbose) {
		print STDERR "Expected numerical argument for -L option; ignored.\n";
		$message_count++;
	    } elsif ($argument < 10) {
		if ($verbose) {
		    print STDERR "Using minimum value of 10 for -L option.\n";
		    $message_count++;
		}
		$Open_file_limit = 10;
	    } else {
		$Open_file_limit = $argument;
	    }
	
	} elsif ($option eq "+L") {
	    $CustomLogging = 1;
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= "$args[$i] ";
	    }
	    $i--; 
	    if ($argument) {
		$argument =~ s/ +$//;
		$argument .= ";" unless $argument =~ /;$/;
		push(@ConfLogging, $argument);
	    }

	} elsif ($option eq "-M") {
	    $do_mx = 0;

	} elsif ($option eq "-m") {
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		($preference, $domain_arg) = split(/:/, $argument, 2);
		$error = 0;
		unless ($preference && $preference =~ /^\d+$/
			&& $preference <= 65535) {
		    print STDERR "Improper -m option; invalid MX preference value ($preference).\n";
		    $error = 1;
		} elsif (!$domain_arg) {
		    print STDERR "Improper -m option; missing MX hostname.\n";
		    $error = 1;
		} elsif ($domain_arg =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		    print STDERR "Uh, the -m option requires a domain name, not an IP address.\n";
		    $error = 1;
		}
		if ($error) {
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		} else {
		    push(@MX, "$preference $domain_arg");
		}
	    }
	    $i--;

	} elsif ($option eq "+m") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= $args[$i];
	    }
	    $i--;
	    $argument =~ s/[,]//g;
	    unless ($argument =~ /^(D|C|P|CP|PC)$/i) {
		print STDERR "Improper `$argument' argument in +m option.\nValid argument must be one of: D, C, P, or CP.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } else {
		$argument = uc($argument);
		if ($multi_homed_mode && $verbose) {
		    print STDERR "Hmm, using `+m $argument' to override previous +m option.\n";
		    $message_count++;
		}
		$multi_homed_mode = $argument;
	    }

	} elsif ($option eq "-N") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -N option; must specify /CIDRsize or subnetmask as an argument.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    if ($argument =~ /^\//) {
		($cidr_size = $argument) =~ s/^\///;
		$Defsubnetmask = undef;
	    } else {
		$cidr_size = undef;
		$Defsubnetmask = $argument;
	    }
	    $error = &CHECK_NET(undef, \$cidr_size, \$Defsubnetmask);
	    if ($error) {
		print STDERR "Improper -N option ($argument):\n$error";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } elsif ($cidr_size < 8) {
		print STDERR "Improper -N option ($argument).\nOnly network sizes /8 to /32 are supported.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }

	} elsif ($option eq "-O") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= "$args[$i] ";
	    }
	    $i--; 
	    $argument =~ s/ $//;
	    push(@BootOptions, $argument);

	} elsif ($option eq "-o") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -o option; must specify an argument of one or more SOA time values.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    unless ("$argument:" =~ /^((\d+|(\d+[wdhms])+)?:){1,5}$/i ||
		$argument =~ /^:{3,4}$/ || $argument !~ /^([+-].*)?$/) {
		print STDERR "Improper -o option `$argument'.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    # As of version 2.40 of `h2n', RFC-2308 status is assumed
	    # unless one of the following two conditions is true:
	    #
	    #   1. We are able to determine that a BIND version earlier
	    #      than 8.2 is running on the master name server.
	    #
	    #   2. The BIND version is unknown *and* exactly four arguments
	    #      (any of which can be null) are specified in the -o option
	    #      *and* no +t option is specified *and* no $TTL directive is
	    #      discovered in an existing DB file.
	    #
	    # Here are some examples that illustrate the various contexts:
	    #
	    #   -o :::          Special case of explicitly specifying `h2n'
	    #                   default SOA values and toggling the default
	    #                   RFC-2308 status to false.
	    #
	    #   -o :30m::       Sets SOA Retry field for all DB files and
	    #                   toggles the default RFC-2308 status to false.
	    #
	    #   -o :::8h        Sets non-RFC-2308 TTL interval in the
	    #                   SOA Minimum field for all DB files and toggles
	    #			the default RFC-2308 status to false.
	    #
	    #   -o :30m::8h     Combines the previous two examples while
	    #                   toggling the default RFC-2308 status to false.
	    #
	    #   -o 3h:1h:1w:1d  Explicitly specifies all four default SOA values
	    #                   and sets the default RFC-2308 status to false.
	    #
	    #   -o ::::         Special case of explicitly specifying `h2n'
	    #                   default SOA values and leaving the default
	    #                   RFC-2308 status to true.
	    #
	    #   -o :30m         Sets SOA Retry field for all DB files but leaves
	    #                   the default RFC-2308 status to true since there
	    #                   are not exactly four arguments specified.
	    #
	    #   -o ::::8h       Sets RFC-2308 $TTL directive for all DB files.
	    #                   SOA Minimum fields either retain existing values
	    #                   or get the default negative caching interval.
	    #
	    #   -o :::30m:      Sets RFC-2308 negative caching interval in the
	    #                   SOA Minimum field for all DB files.
	    #                   $TTL directives either retain existing values
	    #                   or are created with the default TTL value.
	    #
	    #   -o :::30m:8h    Sets RFC-2308 $TTL directive and SOA Minimum
	    #                   field for all DB files.
	    #
	    # The default $TTL value and negative caching interval can
	    # also be set with the +t option.  However, doing so will
	    # set the "$rfc2308" flag to a "hard" value of 2 which only
	    # the FIXUP subroutine can reverse if it detects a BIND
	    # version earlier than 8.2.  The -o option can toggle the
	    # "$rfc2308" flag between 0 (non-RFC-2308 status) and 1
	    # ("soft" RFC-2308 status) as long as +t is not specified.
	    #
	    $tmpTtl = $tmpMasterTtl = "";
	    if ($Refresh || $Retry || $Expire || $Ttl || $MasterTtl) {
		if ($verbose) {
		    print STDERR "Hmm, using `-o $argument' to override previous -o/+t option.\n";
		    $message_count++;
		}
		if ($rfc2308 && ($Ttl || $MasterTtl)) {
		    if ($rfc2308 != 2 && $argument =~ /^([^:]*:){3}[^:]*$/) {
			if ($verbose) {
			    print STDERR "(Ignoring previous \$TTL/Negative cache value(s).)\n";
			    $message_count++;
			}
			$Ttl = $MasterTtl = "";
		    } elsif ($argument !~ /^([^:]*:){3,4}[^:]*$/) {
			if ($verbose) {
			    print STDERR "(Retaining previous \$TTL/Negative cache value(s), however.)\n";
			    $message_count++;
			}
			$tmpTtl = $Ttl if $Ttl;
			$tmpMasterTtl = $MasterTtl if $MasterTtl;
		    }
		}
	    }
	    if ($rfc2308 != 2) {
		$rfc2308 = ($argument =~ /^([^:]*:){3}[^:]*$/) ? 0 : 1;
	    }
	    ($Refresh, $Retry, $Expire, $Ttl, $MasterTtl) = split(':', $argument);
	    $Ttl = $tmpTtl if $tmpTtl;
	    $MasterTtl = $tmpMasterTtl if $tmpMasterTtl;
	    if ($verbose) {
		$message_count++ if &CHECK_SOA_TIMERS;
	    }

	} elsif ($option eq "+O") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= "$args[$i] ";
	    }
	    $i--; 
	    $CustomOptions = 1;
	    if ($argument) {
		$argument =~ s/ +$//;
		$argument .= ";" unless $argument =~ /[;{]$/;
		push(@ConfOptions, $argument);
	    } else {
		$NeedHints = 0;
	    }

	} elsif ($option eq "+om") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= "$args[$i] ";
	    }
	    $i--; 
	    $argument =~ s/ +$//;
	    $argument .= ";" unless $argument =~ /[;{]$/;
	    if ($lastNorD =~ /^-d (.*)/) {
		$MasterZoneOptions{$1} .= "$argument\n";
	    } elsif ($lastNorD =~ /^-n /) {
		($tmp1 = $lastNorD) =~ s/^-n //;
	 	foreach $tmp2 (split(' ', $tmp1)) {
		    $MasterZoneOptions{"db.$tmp2"} .= "$argument\n";
		}
	    } else {
		push(@GlobalMasterZoneOptions, $argument);
	    }

	} elsif ($option eq "+os") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument .= "$args[$i] ";
	    }
	    $i--; 
	    $argument =~ s/ +$//;
	    $argument .= ";" unless $argument =~ /[;{]$/;
	    if ($lastNorD =~ /^-d (.*)/) {
		$SlaveZoneOptions{$1} .= "$argument\n";
	    } elsif ($lastNorD =~ /^-n /) {
		($tmp1 = $lastNorD) =~ s/^-n //;
	 	foreach $tmp2 (split(' ', $tmp1)) {
		    $SlaveZoneOptions{"db.$tmp2"} .= "$argument\n";
		}
	    } else {
		push(@GlobalSlaveZoneOptions, $argument);
	    }

	} elsif ($option eq "-P") {
	    eval { require Tie::CPHash; };
	    if ($@) {
		print STDERR "Ignoring -P option; the required Tie::CPHash Perl module is not installed.\n";
		$message_count++;
	    } else {
		$preserve_case = 1;
	    }

	} elsif ($option eq "-p") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -p option; required domain name argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^mode=/i) {
		    print STDERR "Improper -p option; a domain name must precede the `$argument' argument.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		($tmp = $argument) =~ s/\.+$//;
		if (length($tmp) == 0 || &CHECK_NAME($tmp, 'SOA')) {
		    print STDERR "Improper -p option; the domain name `$argument' is invalid.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		unless ($tmp =~ /\./) {
		    unless ($Domain) {
			print STDERR "Improper -p option; the single-label domain name of `$argument'\nrequires that the -d option be previously specified so that\nthe default fully-qualified domain can be appended.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } else {
			$argument = "$tmp.$Domain";
		    }
		} else {
		    $argument = $tmp;
		}
		($tmp = lc($argument)) =~ s/\./\\./g; 
		if (exists($ptrpatrel{$tmp})) {
		    print STDERR "Improper -p option; the domain `$args[$i]' has\nalready been specified in a prior -p option.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		$ptrpatrel{$tmp} = $argument;
		push(@ptrpats, $tmp);
		$pModeSpec{$tmp} = "";		# a `mode=' arg. may update this

		# Peek ahead and see if there's an optional `mode=' argument.
		#
		if ($#args > $i && $args[$i + 1] =~ /^mode=/i) {
		    ($argument = uc($args[++$i])) =~ s/^MODE=//;
		    $argument =~ s/[,]//g;
		    #
		    # Accept "D" as a valid flag for consistency with the +m
		    # option and the "[mh= ]" comment flag in the host file.
		    # Silently ignore it, however.
		    #
		    unless ($argument =~ /^(A|D|AD|DA|P)$/) {
			print STDERR "Improper `$argument' argument in -p option.\nValid `mode=' value must be one of `A' or `P'.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $argument =~ s/D//;
		    $pModeSpec{$tmp} = $argument; # update with actual flag(s)
		}
	    }
	    $i--;

	} elsif ($option eq "-q") {
	    $verbose = 0;

	} elsif ($option eq "-r") {
	    $do_rp = 1;

	} elsif ($option eq "-S") {
	    $error = 0;
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -S option; required domain name argument is missing.\n";
		$error = 1;
	    } elsif (!$lastNorD) {
		print STDERR "Uh, the -S option is position dependent (applies to preceding -d or -n).\n";
		$error = 1;
	    }
	    if ($error) {
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		    print STDERR "Uh, the -S option requires a domain name, not an IP address.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		} elsif ($lastNorD =~ /^-d (.+)/) {
		    $PartialServers{$1} .= " $argument";
		} elsif ($lastNorD =~ /^-n /) {
		    ($tmp1 = $lastNorD) =~ s/^-n //;
		    foreach $tmp2 (split(' ', $tmp1)) {
			$PartialServers{"db.$tmp2"} .= " $argument";
		    }
		}
	    }
	    $i--;

	} elsif ($option eq "+S") {
	    $argument = "";
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(no|false|off|none|disable)$/i) {
		    $default_supernetting = 0;
		} elsif ($argument =~ /^(yes|true|on|enable)$/i) {
		    $default_supernetting = 1;
		} else {
		    if ($verbose) {
			$check_level = ($default_supernetting) ? "enabled"
							       : "disabled";
			print STDERR "Unknown `+S' argument (`$argument'); supernetting remains $check_level.\n";
			$message_count++;
		    }
		}
	    }
	    $i--;
	    unless ($argument) {
		if ($default_supernetting && $verbose) {
		    print STDERR "Redundant +S option with no argument; supernetting remains enabled.\n";
		    $message_count++;
		}
		$default_supernetting = 1;
	    }

	} elsif ($option eq "-s") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -s option; required domain name argument is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		    print STDERR "Uh, the -s option requires a domain name, not an IP address.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		} else {
		    push(@FullServers, $argument);
		}
	    }
	    $i--;

	} elsif ($option eq "-T") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -T option; must specify at least one of the following arguments:\n";
		print STDERR "                    mode=M, RR=, and/or ALIAS=\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^mode=M/i) {
		    $do_zone_apex_mx = 1;
		} elsif ($argument =~ /^ALIAS=/i) {
		    ($domain_arg = $argument) =~ s/^ALIAS=//i;
		    unless (length($domain_arg)) {
			print STDERR "Improper -T option; the `ALIAS=' keyword requires a domain name as an argument.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    ($domain_arg, $ttl) = split(' ', $domain_arg, 2);
		    $ttl = "" unless defined($ttl);
		    if (exists($Apex_aliases{lc($domain_arg)})) {
			print STDERR "Improper -T option; duplicate argument `$argument' ignored.\n" if $verbose;
			$message_count++;
		    } elsif ($ttl && $ttl !~ /^(\d+|(\d+[wdhms])+)$/i) {
			print STDERR "Improper -T option; invalid TTL in argument `$argument'.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } else {
			$Apex_aliases{lc($domain_arg)} = "$domain_arg $ttl";
		    }
		} elsif ($argument =~ /^RR=/i) {
		    ($token = $argument) =~ s/^RR=//i;
		    unless (length($token)) {
			print STDERR "Improper -T option; the `RR=' keyword requires a DNS record as an argument.\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $error = "";
		    if ($token =~ /^(.*?\s)($RRtypes)\s+(.+)/io) {
			$tmp1 = $1;
			$rrtype = uc($2);
			$rdata = $3;
			$tmp1 =~ s/\s+IN\s+$//i;	# strip `IN' if present
			$domain_arg = '@';		# set to default value
			if ($tmp1 =~ /^\S/) {		# $ttl *may* be null
			    ($domain_arg, $ttl) = split(' ', $tmp1);
			    1 while $domain_arg =~ s/\\\\/\\/g;
			} elsif ($tmp1 =~ /\S/) {	# $ttl is *not* null
			    ($ttl = $tmp1) =~ s/\s+//g;
			} else {			# $ttl *is* null
			    $ttl = "";
			}
			if ($domain_arg ne '@' && lc($domain_arg) ne lc("$Domain.")) {
			    $error = "the `RR=' owner field must be `\@' if specified.";
			} elsif ($rrtype eq 'SOA') {
			    $error = "the SOA record is configured with -h/-i/-o/+t/-u/-y.";
			} elsif ($rrtype eq 'NS') {
			    $error = "zone apex NS records may only be configured with -s/-S.";
			} elsif ($rrtype !~ /^($apex_RRtypes)$/o) {
			    $error = "`$rrtype' is out of context as a zone apex RR type.";
			} elsif ($ttl && $ttl !~ /^(\d+|(\d+[wdhms])+)$/i) {
			    $error = "invalid TTL ($ttl).";
			}
		    } else {
			$error = "the `RR=' argument has a bad format or an unknown RR type.";
		    }
		    if ($error) {
			print STDERR "Improper -T option; $error\n";
			$argument =~ s/=.*//;
			print STDERR "> $argument='$token'\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    }
		    $continuation_line = $open_paren_count = $open_quote = 0;
		    while ($continuation_line || $rdata =~/["()]/) {
			#
			# Accommodate the possibility that the -T option
			# may specify a multi-line resource record, e.g.,
			#
			#   -T RR=" WKS 192.249.239.3  TCP ( telnet smtp"
			#         "                    ftp shell domain)"
			#   -T RR=' TXT "First line of text\'
			#         'second line of text."'
			#
			# NOTE: The shell and/or the -f option has already
			#       ensured that any quotes needed to protect
			#       whitespace or other special characters in
			#       the "@args" argument stream are already
			#       balanced.  These 'outer' quotes are
			#       effectively removed as each argument is
			#       assembled and thus are not seen here.
			#       Unescaped "inner" quotes that are part of
			#       an RDATA field, however, are seen and
			#       accounted for by the -T option.  Unbalanced
			#       quotes and/or parentheses will cause subsequent
			#       arguments to be appended until balance is
			#       achieved or there are no more arguments left
			#       in "@args".
			#
			$token = $rdata;
			$last_char = "";
			while (length($token)) {
			    ($char, $token) = split(//, $token, 2);
			    if ($char eq "\\" && $last_char eq "\\") {
				#
				# An escape character which is itself escaped
				# becomes an ordinary backslash character.
				# Remove its ability to escape the next
				# character in the byte stream.
				#
				$last_char = "";
				next;
			    }
			    unless ($open_quote || $last_char eq "\\") {
				last if $char eq ";";
				if ($char eq "\(") {
				    $open_paren_count++;
				    #
				    # Maintain a stack of arguments which have
				    # open quotes and/or parentheses.  Arguments
				    # are popped from the stack as the balancing
				    # characters are read.  Leftover arguments
				    # on the stack are reported along with the
				    # appropriate error message to help locate
				    # the source of the imbalance.
				    #
				    $j = $#unbalanced_args;
				    if ($j < 0 || $unbalanced_args[$j][0] ne $rdata) {
					push(@unbalanced_args, [ $rdata, 0 ]);
				    } else {
					$unbalanced_args[$j][1]++;
				    }
				} elsif ($char eq "\)") {
				    $open_paren_count--;
				    $j = $#unbalanced_args;
				    if ($open_paren_count < 0) {
					@unbalanced_args = ();
					push(@unbalanced_args, $rdata);
					last;
				    }
				    if ($unbalanced_args[$j][1]) {
					$unbalanced_args[$j][1]--;
				    } else {
					pop(@unbalanced_args);
				    }
				}
			    }
			    if ($char eq '"' && $last_char ne "\\") {
				$open_quote = !$open_quote;
				$j = $#unbalanced_args;
				if ($open_quote) {
				    if ($j < 0 || $unbalanced_args[$j][0] ne $rdata) {
					push(@unbalanced_args, [ $rdata, 0 ]);
				    } else {
					$unbalanced_args[$j][1]++;
				    }
				} else {
				    if ($unbalanced_args[$j][1]) {
					$unbalanced_args[$j][1]--;
				    } else {
					pop(@unbalanced_args);
				    }
				}
			    }
			    $last_char = $char;

			}
			if ($continuation_line) {
			    #
			    # Append a newline as a way of signaling FIXUP()
			    # and MAKE_SOA() that this is a multi-line record.
			    # A second newline is appended if an open quote
			    # is in effect when the line break occurs.
			    #
			    $rdata .= "\n";
			    $rdata .= "\n" if $continuation_line == 2;
			} else {
			    #
			    # Prepend the record's TTL to the beginning
			    # of each new RR.
			    #
			    $rdata = "$ttl,$rdata";
			}
			push(@{ $Apex_RRs{$rrtype} }, $rdata);
			unless ($open_quote || $open_paren_count) {
			    $rdata = "";
			    $continuation_line = 0;
			} elsif ($open_paren_count < 0) {
			    $error = "found `RR=' argument with a misplaced closing parenthesis:\n";
			    $error .= "> `$unbalanced_args[0]'";
			    last;
			} else {
			    #
			    # Get the next token in the PARSE_ARGS() argument
			    # vector provided there is one that's available.
			    #
			    if ($#args > $i) {
				$continuation_line = ($open_quote) ? 2 : 1;
				$rdata = $args[++$i];
			    } else {
				if (!$open_quote) {
				    $tmp1 = "parentheses.\n";
				} elsif (!$open_paren_count) {
				    $tmp1 = "quotes.\n";
				} else {
				    $tmp1 = "parentheses and\nquotes.  ";
				}
				$error = "found `RR=' argument(s) with unbalanced $tmp1";
				$error .= "Ran out of arguments before the following item(s) could be balanced:";
				for $j (0 .. $#unbalanced_args) {
				    $error .= "\n> `$unbalanced_args[$j][0]'";
				}
				last;
			    }
			}
		    }
		    if ($error) {
			print STDERR "Improper -T option; $error\n";
			&GIVE_UP unless defined wantarray;
			$message_count++;
			$i++;
			last;
		    } elsif ($rdata) {
			#
			# An ordinary single-line RR was specified.
			#
			$rdata = "$ttl,$rdata";
			push(@{ $Apex_RRs{$rrtype} }, $rdata);
		    }
		} else {
		    print STDERR "Improper -T option; argument `$argument' is unrecognized.\n";
		    print STDERR "                    Must be one of mode=M, RR=, and/or ALIAS=\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}

	    }
	    $i--;

	} elsif ($option eq "-t") {
	    $do_txt = 1;

	} elsif ($option eq "+t") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper +t option; must specify at least a DEFAULT-TTL argument.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    unless ($argument =~ /^(\d+|(\d+[wdhms])+)$/i) {
		print STDERR "Improper +t option `$argument' for DEFAULT-TTL.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } else {
		$MasterTtl = $argument;
	    }
	    unless ($#args > $i && ($args[$i + 1] =~ /^($h2n_opts)$/o ||
				    $args[$i + 1] =~ /^($verify_opts)$/io)) {
		$argument = $args[++$i];
		unless ($argument =~ /^(\d+|(\d+[wdhms])+)$/i) {
		    print STDERR "Improper +t option `$argument' for MINIMUM-TTL.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    next;
		} else {
		    $Ttl = $argument;
		}
	    } else {
		$Ttl = $DefNegCache;
	    }
	    if (!$rfc2308 && $verbose) {
		print STDERR "Hmm, using `+t $MasterTtl $Ttl' to override previous -o option.\n";
		$message_count++;
	    }
	    # NOTE: Once the `+t' option is specified, our RFC-2308 status
	    #       can not be undone by a subsequent four-argument `-o'
	    #       option, e.g., `-o :::', in this subroutine (PARSE_ARGS).
	    #       The "$rfc2308" flag is set to a "hard" value of 2.
	    #       The only way for non-RFC-2308 status to be asserted now,
	    #       i.e., "$rfc2308" = 0, is if the FIXUP subroutine detects
	    #       that a pre-8.2 version of BIND is running on the master
	    #       name server (-h option).
	    #
	    $rfc2308 = 2;
	    if ($verbose) {
		$message_count++ if &CHECK_SOA_TIMERS;
	    }

	} elsif ($option eq "-u") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -u option; the zone contact mail address (SOA RNAME) is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    $argument = $args[++$i];
	    if ($argument eq '@') {
		$argument = "root";
		print STDERR "`\@' is not acceptable for the -u option; substituting `root' instead.\n" if $verbose;
		$message_count++;
	    }
	    $User = $argument;

	} elsif ($option eq "-V") {
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = lc($args[$i]);
		#
		# Append the root zone as a trailing dot and then
		# make sure that there is only one such dot.
		#
		$argument .= ".";
		$argument =~ s/\.+$/./;
		#
		# Stack the domains with the unshift() function so that they
		# can be popped in the same order.  This allows child domains
		# to be pushed onto the stack during processing and thus grouped
		# with the parent domain if recursive verification is enabled.
		#
		unshift(@Vdomains, $argument) unless exists($duplicate{$argument});
		$duplicate{$argument} = 1;
	    }
	    $verify_mode = 1;
	    $i--;

	} elsif ($option =~ /^-v([:=](.*))?$/) {
	    #
	    # This option has two functions.  The documented one is to
	    # report the version number of `h2n'.  The undocumented
	    # function is to set the "$BIND_version" variable as an aid
	    # in debugging the BIND bugs reported by GET_BIND_VERSION().
	    #
	    if (defined $2) {
		$debug_BIND_version = $2;
	    } else {
		print STDOUT "This is $0 v$VERSION\n";
		exit(0) unless defined wantarray;
	    }

	} elsif ($option eq "-W") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -W option; the zone file pathname is missing.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    ($Pwd = $args[++$i]) =~ s/\/+$//;
	    unless ($Pwd =~ /^\//) {
		print STDERR "Must use an absolute pathname for -W option.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }

	} elsif ($option eq "-w") {
	    $do_wks = 1;

	} elsif ($option eq "-y") {
	    $argument = "D";
	    if (defined($NewSerial) && !$UseDateInSerial) {
		print STDERR "Improper -y option; it is incompatible with the already-specified -i option.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    } elsif ($#args > $i && $args[$i + 1] =~ /^mode=/i) {
		$token = $args[++$i];
		($argument = uc($token)) =~ s/^MODE=//;
		unless ($argument =~ /^[DM]$/) {
		    print STDERR "Improper `$token' argument in -y option.\nThe component of a valid `mode=' value is either `D' or `M'.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    next;
		}
	    }
	    if ($argument =~ /^[DM]$/) {
		@ctime = localtime(time);
		$NewSerial = (($ctime[5] + 1900) * 1000000)
			   + (($ctime[4] + 1) * 10000);
		if ($argument eq "M") {
		    $UseDateInSerial = 1;
		} else {
		    $UseDateInSerial = ($ctime[3] * 100);
		    $NewSerial += $UseDateInSerial;
		}
	    }

	} elsif ($option eq "-Z") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -Z option; must specify at least one name server IP address.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		    if (($4 > 255 || $3 > 255 || $2 > 255 || $1 > 255)
			|| $argument =~ /(^|\.)0\d/) {
			$tmp = 0;
		    } else {
			$tmp = 1;
		    }
		} else {
		    $tmp = 0;
		}
		unless ($tmp) {
		    print STDERR "Improper IP address [$argument] in -Z option.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		if (defined($Bootsecaddr)) {
		    $Bootsecaddr .= " " . $argument;
		} else {
		    $Bootsecaddr = $argument;
		}
		if (defined($Confsecaddr)) {
		    $Confsecaddr .= " " . $argument . ";";
		} else {
		    $Confsecaddr = $argument . ";";
		}
	    }
	    $i--;

	} elsif ($option eq "-z") {
	    unless ($#args > $i && $args[$i + 1] !~ /^($h2n_opts)$/o
				&& $args[$i + 1] !~ /^($verify_opts)$/io) {
		print STDERR "Improper -z option; must specify at least one name server IP address.\n";
		&GIVE_UP unless defined wantarray;
		$message_count++;
		$i++;
		next;
	    }
	    while (++$i <= $#args && $args[$i] !~ /^($h2n_opts)$/o
				  && $args[$i] !~ /^($verify_opts)$/io) {
		$argument = $args[$i];
		if ($argument =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
		    if (($4 > 255 || $3 > 255 || $2 > 255 || $1 > 255)
			|| $argument =~ /(^|\.)0\d/) {
			$tmp = 0;
		    } else {
			$tmp = 1;
		    }
		} else {
		    $tmp = 0;
		}
		unless ($tmp) {
		    print STDERR "Improper IP address [$argument] in -z option.\n";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    $i++;
		    last;
		}
		if (defined($Bootsecsaveaddr)) {
		    $Bootsecsaveaddr .= " " . $argument;
		} else {
		    $Bootsecsaveaddr = $argument;
		}
		if (defined($Confsecsaveaddr)) {
		    $Confsecsaveaddr .= " " . $argument . ";";
		} else {
		    $Confsecsaveaddr = $argument . ";";
		}
	    }
	    $i--;

	} elsif ($option eq "-1" && $verbose) {
	    print STDERR "Option -1 is obsolete ... ignored.\n";
	    $message_count++;

	} elsif ($option eq "-F" && $verbose) {
	    print STDERR "Option -F is now the default (and only) way ... ignored.\n";
	    $message_count++;

	} elsif ($option =~ /^-(no-?)?recurs(e|ive|ion)$/i) {
	    #
	    # Causes each child domain to be verified immediately
	    # after completing verification of the parent domain.
	    #
	    $recursive_verify = ($option =~ /^-no/) ? 0 : 1;

	} elsif ($option =~ /^-(no-?)?(check|verify)[_-]?del$/i) {
	    #
	    # Bypasses the checking of proper delegations with the
	    # `check_del' program when in verify mode.  `check_del'
	    # can be quite time-consuming when encountering a large
	    # number of unresponsive name servers.
	    #
	    $verify_delegations = ($option =~ /^-no/) ? 0 : 1;

	} elsif ($option =~ /^-(no-?)?show-?(dangling|nxdomain)-?cnames?$/i) {
	    #
	    # CNAMEs that point to non-existent domain names, i.e.,
	    # "dangling CNAMEs", are not generally considered to be
	    # DNS errors.  This is especially true in the context
	    # of RFC-2317 delegations of sub-class-C `in-addr.arpa'
	    # zones where each CNAME placeholder does not necessarily
	    # point to an existing domain name having a PTR record.
	    # For forward-mapping zones, however, it may be of interest
	    # to know if a CNAME has become obsolete if the out-of-zone
	    # domain name to which it points no longer longer exists.
	    #
	    $show_nxdomain_cnames = ($option =~ /^-no/) ? 0 : 1;

	} elsif ($option =~ /^-(no-?)?show-?(cname|chained)-?(chain|cname)s?$/i) {
	    #
	    # The length of out-of-zone CNAME chains are normally
	    # displayed only if the CNAME ultimately fails to resolve
	    # to a non-CNAME domain name.  This option overrides the
	    # default behavior by identifying all instances of
	    # out-of-zone CNAME chains.
	    #
	    $show_chained_cnames = ($option =~ /^-no/) ? 0 : 1;

	} elsif ($option =~ /^-(no-?)?debug(:(.*))?$/) {
	    #
	    # Prevents the removal of all temporary files that get
	    # created during the course of normal processing including
	    # zone transfer files:
	    #
	    #  * If zone auditing is in effect, the DiG batch input file
	    #    is saved as well as a copy of the answers to the queries.
	    #  * If delegations are being verified, the complete input and
	    #    output of the `check_del' program is also saved.
	    #  * If a domain is being verified and the zone transfer file
	    #    still exists from a previous run with -debug, the existing
	    #    zone transfer data will be used instead of requesting a
	    #    new copy from an authoritative name server.
	    #
	    # An alternate directory to `/tmp' may be specified by using
	    # the format `-debug:directory'.  The directory must exist
	    # and the user running `h2n' must have read/write access.
	    #
	    $debug_DIR = $3;
	    if ($option =~ /^-no/) {
		$debug_DIR = "/tmp";
		$debug = 0;
	    } else {
		$debug = 1;
		if ($debug_DIR) {
		    $debug_DIR =~ s/\/+$//;
		    $debug_DIR = "/" unless $debug_DIR;
		    if (!-d $debug_DIR) {
			if ($verbose) {
			    print STDERR "`$debug_DIR' argument of -debug must be a directory; ignored.\n";
			    $message_count++;
			}
			$debug_DIR = "/tmp";
		    } elsif (!-r $debug_DIR || !-w $debug_DIR) {
			if ($verbose) {
			    print STDERR "`$debug_DIR' argument of -debug requires R/W access; ignored.\n";
			    $message_count++;
			}
			$debug_DIR = "/tmp";
		    }
		    $debug_DIR = "" if $debug_DIR eq "/";
		} else {
		    $debug_DIR = "/tmp";
		}
	    }

	} elsif ($option eq "-\?") {
	    print STDOUT <<EOT;
Usage:  h2n [zone creation options] | -V [zone verification options]

The zone creation options are:
  -A Don't create name server data for aliases in the host table
  -a NET[:SUBNETMASK|/CIDRsize [mode=S]] [NET ...]
     Add hostname data on NET to -d DOMAIN but without PTR data
     mode=S  Allow /8-24 network to be a supernet to smaller-classed nets
  -B PATH
     Sets absolute directory path where boot/conf files will be written
  -b BOOTFILE
     Use BOOTFILE instead of the default: ./named.boot (BIND 4)
  -C COMMENT-FILE
     Create RRs using special host file comments as keys into COMMENT-FILE
  +C [PRE-CONFFILE]
     Prepend contents of PRE-CONFFILE to the BIND 8/9 conf file (+c option)
  -c REMOTE-DOMAIN [mode=[A][I][D[Q]]] [REMOTE-DOMAIN [mode=[A][I][D[Q]]]]
     Add CNAMEs which point to REMOTE-DOMAIN
     mode=A  Create additional CNAMEs for aliases in REMOTE-DOMAIN
         =I  REMOTE-DOMAIN is an intra-zone subdomain of -d DOMAIN
         =D  Defer CNAMEs; name conflicts prefer -d DOMAIN over REMOTE-DOMAIN
         =Q  Don't report name conflicts that prevent deferred CNAME creation
  +c [CONFFILE] [mode=[S|M]
     Use CONFFILE instead of the default: ./named.conf (BIND 8/9)
     mode=S  Create CONFFILE with zone entries in single-line format (default)
         =M  Create CONFFILE with zone entries in multi-line format
  -d DOMAIN [db=FILE1] [spcl=FILE2] [mode=D]
     Create zone data file for DOMAIN
     db=FILE1    Override default filename of db.LABEL, e.g., label.movie.edu
     spcl=FILE2  Override default filename of spcl.LABEL for existing RRs
     mode=D      Set default domain of unqualified hostnames to DOMAIN
  -e EXCLUDED-DOMAIN [EXCLUDED-DOMAIN]
     Exclude hostfile data with names in EXCLUDED-DOMAIN
  -f FILE
     Read command line options from FILE
  -H HOSTFILE
     Use HOSTFILE instead of /etc/hosts
  -h HOST
     Set HOST in the MNAME (master name server) field of the SOA record
  -I [ignore|warn|audit|audit-only|warn-strict|fail|strict] [rfc2782]
     Control level and type of various RFC conformance checks
     ignore       Disables checking of domain names and zone data consistency
     warn         Issue warning when hostnames contain illegal characters
     audit        Check zone data for integrity and RFC compliance + `warn'
     audit-only   Check zone data integrity without the `warn' check
     warn-strict  Warn about single-character hostnames + `warn' + `audit'
     fail         Reject hostnames with illegal characters + `audit'
     strict       Reject single-character hostnames + `fail' + `audit'
     rfc2782      Check SRV RRs for `_service._protocol' labels in owner names
  -i NUM
     Set the serial number of the zone to NUM
  -L NUM
     Set file handle limit to NUM
  +L [LOG-SPEC]
     Add a logging specification to the BIND 8/9 config files
  -M Don't generate MX records
  -m WEIGHT:MX-HOST [WEIGHT:MX-HOST]
     Include MX record for each host not having a [no mx] flag
  +m [D|C|P|CP]
     Control RR generation method for multi-homed hosts
     D   Use default behavior (A RRs for all names, CNAMEs for common aliases)
     C   Create A RRs for canonical name and 1st alias, CNAMEs for all others
     P   Create PTR RRs that point to A RR of 1st alias instead of canonical
     CP  Combine `C' and `P' flags
  -N SUBNETMASK|/CIDRsize
     Apply SUBNETMASK/CIDRsize as default value for subsequent -n/-a options
  -n NET[:SUBNETMASK|/CIDRsize [mode=S] [domain=DOMAIN] [ptr-owner=TEMPLATE]]
     Create zone data for each class-A/B/C subnet of NET for network sizes
     /8 to /24.  For /25-32 networks, create zone data to support RFC-2317
     delegations to DOMAIN with the owner names of the PTR records fitting
     the TEMPLATE pattern.
     mode=S  Allow /8-24 network to be a supernet to smaller-classed nets
  -O OPTION OPTION-ARGS
     Add option specifications to BIND 4 boot files
  +O [OPTION-SPEC]
     Add option specifications to BIND 8/9 conf files
  -o [REFRESH]:[RETRY]:[EXPIRE]:[MINIMUM]:[DEFAULT-TTL]
     Set SOA time intervals
 +om OPTION OPTIONS-ARGS
     Adds zone-specific options to BIND 8/9 master conf
 +os OPTION OPTIONS-ARGS
     Adds zone-specific options to BIND 8/9 slave conf
  -P Preserve upper-case characters of hostnames and aliases in the host table
  -p REMOTE-DOMAIN [mode=[A|P] [REMOTE-DOMAIN [mode=[A|P]]
     Create only PTR data for REMOTE-DOMAIN hosts
     mode=A  Required flag if REMOTE-DOMAIN's forward-mapping zone built w/ -A
         =P  Enables alternate method of PTR generation as described for +m P
  -q Work quietly
  -r Enable creation of RP (Responsible Person) records
  -S SERVER [SERVER]
     Adds NS record to zone(s) for the last preceding -d or -n options
  +S [enable|disable]
     Control class-A/B/C NETs to act as supernets for subsequent -n/-a options
  -s SERVER [SERVER]
     Adds NS record to zones for -d option and all -n options
  -T [mode=M] [RR='DNS RR' [RR='...']] [ALIAS='name [TTL]' [ALIAS='...']]
     Add additional top-of-zone-related records to DOMAIN of the -d option
     mode=M  Add the global MX record(s) specified in the -m option
     RR=     Add 'DNS RR' with owner field set to whitespace or to `\@'
     ALIAS=  Add CNAME RR with owner field of 'name' & RDATA field set to `\@'
  -t Generate TXT records from the host table comment section
  +t DEFAULT-TTL [MINIMUM-TTL]
     Create \$TTL directives & SOA Negative Cache TTL
  -u CONTACT
     Set CONTACT as the mail addr. in the SOA RNAME (responsible person) field
  -v Display the version number of h2n
  -W PATH
     Sets absolute directory pathname where zone files will be located
  -w Generate WKS records for SMTP/TCP for every MX RRset
  -y [mode=[D|M]
     Set SOA serial numbers to use date/version format
     mode=D  Set day format of YYYYMMDDvv allowing 100 versions/day (default)
         =M  Set month format of YYYYMMvvvv allowing 10,000 versions/month
  -Z ADDRESS [ADDRESS]
     Specify ADDRESS of primary from which to load unsaved zone data
  -z ADDRESS [ADDRESS]
     Specify ADDRESS of primary from which to load saved zone data
  -show-nxdomain-cnames [-no-show-nxdomain-cnames]
     Report CNAMEs that point to non-existent out-of-zone domain names or
     domain names with no RRs if auditing is in effect (default is -show)
  -show-chained-cnames [-no-show-chained-cnames]
     Display each out-of-zone chained CNAME if auditing (default is -no)
  -debug[:directory] [-no-debug]
     Prevent removal of temp files in /tmp or [directory] (default is -no)

The zone verification options are:
  -f FILE
     Read command line options from FILE
  -v Display the version number of h2n
  -I [audit|audit-only]
     Control level and type of various RFC conformance checks
     audit       Check zone data integrity & report names with illegal chars.
     audit-only  Check zone data integrity & ignore names with illegal chars.
  -V DOMAIN [DOMAIN]
     Verify the integrity of a domain obtained by an AXFR query
  -recurse [-no-recurse]
     Recursively verify delegated subdomains (default is -no)
  -show-nxdomain-cnames [-no-show-nxdomain-cnames]
     Report CNAMEs that point to non-existent out-of-zone domain names
     if auditing is in effect (default is -show)
  -show-chained-cnames [-no-show-chained-cnames]
     Display each out-of-zone chained CNAME (default is -no)
  -check-del [-no-check-del]
     Check delegation of all discovered NS RRs (default)
  -debug[:directory] [-no-debug]
     Prevent removal of temp files in /tmp or [directory] (default is -no)
     Zone data temp file is re-verified instead of making a new AXFR query.

This is $0 v$VERSION
EOT
	    exit(0) unless defined wantarray;
	    $message_count++;

	} elsif ($option =~ /^[+-].+/ && $verbose) {
	    print STDERR "Unknown option `$option'; ignored.\n";
	    $message_count++;

	} elsif ($verbose) {
	    print STDERR "Extraneous input `$option'; ignored.\n";
	    $message_count++;
	}
	$i++;
    }
    
    $net = keys(%Net_ranges);
    if ($verify_mode) {
	unless (@Vdomains) {
	    print STDERR "The -V option requires at least one domain name as an argument.\n";
	    &GIVE_UP unless defined wantarray;
	    $message_count++;
	}
	if ($net || $Domain || defined($User)) {
	    print STDERR "The -d, -n/-a, and/or -u options are incompatible with -V.\n";
	    &GIVE_UP unless defined wantarray;
	    $message_count++;
	}
    } elsif (!$do_aliases && $multi_homed_mode =~ /[CP]/) {
	print STDERR "`+m $multi_homed_mode' option incompatible with -A option.\n";
	&GIVE_UP unless defined wantarray;
	$message_count++;
    } elsif (!defined(wantarray) &&
	     (!$net || !$Domain || !defined($User))) {
	print STDERR "Must specify at least -d, one -n/-a, and -u.\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    } else {
	$Search_dir = ".";
	if (@cpats) {
	    $argument = 0;
	    foreach $tmp (@cpats) {
		$domain_arg = $cpatrel{$tmp};
		$argument = 1 if lc($domain_arg) eq lc($Domain);
		if ($argument) {
		    ($message = <<EOT) =~ s/^\s+\|//gm;
	    |The `$domain_arg' domain name argument is ambiguously
	    |specified.  Its appearance in the -d option is incompatible
	    |with a simultaneous specification in the -c option.
EOT
		    print STDERR "$message";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    last;
		}
	    }
	}
	if (@elimpats) {
	    $argument = "";
	    foreach $tmp (@elimpats) {
		($domain_arg = $tmp) =~ s/\\[.]/./g;
		if ($domain_arg eq lc($Domain)) {
		    $argument = "-d option";
		} else {
		    if (exists($cpatrel{$tmp})) {
			$argument = "-c option";
		    }
		    if (exists($ptrpatrel{$tmp})) {
			if ($argument) {
			    $argument = "-c and -p options"; 
			} else {
			    $argument = "-p option";
			}
		    }
		}
		if ($argument) {
		    ($message = <<EOT) =~ s/^\s+\|//gm;
	    |The `$domain_arg' domain name argument is ambiguously
	    |specified.  Its appearance in a -e option is incompatible
	    |with a simultaneous specification in the $argument.
EOT
		    print STDERR "$message";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    last;
		}
	    }
	}
	if (@ptrpats) {
	    $message = "";
	    foreach $tmp (@ptrpats) {
		$domain_arg = $ptrpatrel{$tmp};
		if (lc($domain_arg) eq lc($Domain)) {
		    ($message = <<EOT) =~ s/^\s+\|//gm;
	    |The `$domain_arg' domain name argument is ambiguously
	    |specified.  Its appearance in the -d option is incompatible
	    |with a simultaneous specification in the -p option.
EOT
		} elsif (exists($cpatrel{$tmp}) && $cModeSpec{$tmp} =~ /I/) {
		    ($message = <<EOT) =~ s/^\s+\|//gm;
	    |Improper `-p $domain_arg' option; the domain name
	    |matches that of a -c option which has been flagged with `mode=I'
	    |to indicate that it is an intra-zone subdomain of the -d option.
	    |The -p option is restricted to domains that are in a different
	    |DNS zone than the -d option ($Domain).
EOT
		}
		if ($message) {
		    print STDERR "$message";
		    &GIVE_UP unless defined wantarray;
		    $message_count++;
		    last;
		}
	    }
	}
    }
    return $message_count;
}


# Validate a network specification passed in one of the following formats:
#
#   (network, CIDRsize, netmask="")
#   (network, CIDRsize="", netmask)
#   (undef, CIDRsize, netmask="")
#   (undef, CIDRsize="", netmask)
#
# where network matches a format of x[.x[.x[.x]]]
#       CIDRsize is an integer from 1 to 32
#       netmask matches the format of x.x.x.x
#
# * A valid `network/CIDRsize' specification returns the corresponding netmask
#   and normalizes the network to its minimum number of octets.
# * A valid `network:netmask' specification returns the corresponding CIDR
#   size and normalizes the network to its minimum number of octets.
# * A valid `CIDRsize' specification returns the corresponding netmask.
# * A valid `netmask' specification returns the corresponding CIDRsize.
#
# NOTE: Parameters *must* be passed using the following convention:
#
#       Input variables - passed by *reference* and subject to change
#       Output variable - initialized to "" and passed by *reference*
#       Void parameter  - the undefined value passed as a placeholder
#
# Returns: the undefined value or null string if no error
#          an error string otherwise
#
#
#############################################################################
#
#                        NETWORKING TERMINOLOGY
#                        ======================
#
#                   Determining the Class of an Address
#                   -----------------------------------
#
#    Given a 32-bit address, xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx,
#
#    0xxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx  Class A  1-127.x.x.x
#    10xxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx  Class B  128-191.x.x.x
#    110xxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx  Class C  192-223.x.x.x
#    1110xxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx  Class D  224-239.x.x.x (multicast)
#    1111xxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx  Class E  240-255.x.x.x (experimental)
#
#    The original concept of network classes has given way to a
#    more efficient way of allocating networks of IP addresses 
#    called Classless InterDomain Routing or CIDR.
#
#
#                       CIDR Conversion Table
#                       ---------------------
#
#       CIDR
#      Length        Mask         # Networks    # Addresses
#      ------   ---------------   ----------   -------------
#        /0     0.0.0.0           special wildcard mask used in ACLs
#                                 to match any address to [0.0.0.0]
#        /1     128.0.0.0           128 A      2,147,483,648  
#        /2     192.0.0.0            64 A      1,073,741,824  
#        /3     224.0.0.0            32 A        536,870,912  
#        /4     240.0.0.0            16 A        268,435,456  
#        /5     248.0.0.0             8 A        134,217,728  
#        /6     252.0.0.0             4 A         67,108,864  
#        /7     254.0.0.0             2 A         33,554,432 
#        /8     255.0.0.0             1 A         16,777,216  
#        /9     255.128.0.0         128 B          8,388,608  
#        /10    255.192.0.0          64 B          4,194,304  
#        /11    255.224.0.0          32 B          2,097,152  
#        /12    255.240.0.0          16 B          1,048,576  
#        /13    255.248.0.0           8 B            524,288  
#        /14    255.252.0.0           4 B            262,144  
#        /15    255.254.0.0           2 B            131,072  
#        /16    255.255.0.0           1 B             65,536  
#        /17    255.255.128.0       128 C             32,768  
#        /18    255.255.192.0        64 C             16,384  
#        /19    255.255.224.0        32 C              8,192  
#        /20    255.255.240.0        16 C              4,096  
#        /21    255.255.248.0         8 C              2,048  
#        /22    255.255.252.0         4 C              1,024  
#        /23    255.255.254.0         2 C                512 
#        /24    255.255.255.0         1 C                256  
#        /25    255.255.255.128    2 subnets             128  
#        /26    255.255.255.192    4 subnets              64  
#        /27    255.255.255.224    8 subnets              32  
#        /28    255.255.255.240   16 subnets              16  
#        /29    255.255.255.248   32 subnets               8  
#        /30    255.255.255.252   64 subnets               4  
#        /31    255.255.255.254       none                 2
#        /32    255.255.255.255     1/256 C                1
#
#
#
#                      RFC-1918 Reserved Network Numbers
#                      ---------------------------------
#
#  The following networks are reserved for use by entities which do not 
#  require globally unique address space.  The obvious advantage for the
#  Internet at large is the conservation of globally unique address space
#  by not using it where global uniqueness is not required.
#
#  Class    Start         End          # Addrs              Comment
#  ----- ----------- --------------- ----------  -------------------------------
#    A   10.0.0.0    10.255.255.255  16,777,216    a single Class A network
#    B   172.16.0.0  172.31.255.255   1,048,576   16 contiguous Class B networks
#    C   192.168.0.0 192.168.255.255     65,536  256 contiguous Class C networks
#
#
sub CHECK_NET {
    my ($network_ref, $cidr_ref, $mask_ref) = @_;
    my ($bit_num, $cidr, $error, $error_status, $factor, $int_mask, $mask);
    my ($network, $num_octets, $octet, $octet_1, $octet_2, $octet_3, $octet_4);
    my ($octet_count, $remainder, $rightmost_octet);

    $error = "";
    $error_status = 0;

    if (defined($network_ref)) {
	$network = $$network_ref;
	unless ($network =~ /^\d+([.]\d+){0,3}$/) {
	    $error = " Invalid network specification.\n";
	} else {
	    ($octet_1, $octet_2, $octet_3, $octet_4) = split(/\./, $network, 4);
	    if (defined($octet_4)) {
		$num_octets = 4;
		$error_status = 1 if $octet_4 > 255;
	    } elsif (defined($octet_3)) {
		$num_octets = 3;
		$error_status = 1 if $octet_3 > 255;
	    } elsif (defined($octet_2)) {
		$num_octets = 2;
		$error_status = 1 if $octet_2 > 255;
	    } else {
		$num_octets = 1;
		$error_status = 1 if ($octet_1 == 0 or $octet_1 > 255);
	    }
	    $error = " Invalid network specification (octet out of range).\n" if $error_status;
	}
    }
    $cidr = $$cidr_ref;
    $mask = $$mask_ref;
    if (defined($cidr)) {
	unless ($cidr =~ /^\d+$/) {
	    $error .= " Invalid CIDR specification.\n";
	} elsif ($cidr < 1 || $cidr > 32) {
	    $error .= " Invalid CIDR size (must be 1 to 32).\n";
	}
    } else {
	unless (defined($mask) && $mask =~ /^\d+([.]\d+){3}$/) {
	    $error .= " Invalid netmask specification.\n";
	} else {
	    #
	    # Prepare a 32-bit integer version of the netmask.
	    #
	    $int_mask = $octet_count = 0;
	    foreach $octet (split(/\./, $mask)) {
		last if $octet > 255 || ($octet == 0 && $octet_count == 0);
		$octet_count++;
		$int_mask += $octet;
		$int_mask = $int_mask << 8 if $octet_count < 4;
	    }
	    if ($octet_count < 4) {
		$error .= " Invalid netmask specification (octet out of range).\n";
	    } else {
		#
		# Start inspecting the 32-bit mask from right to left.
		# Once a "1" is found, make sure that there are no
		# intervening "0"s between that point and the leftmost bit.
		#
		$cidr = 0;
		foreach $bit_num (reverse 1..32) {
		    if ($int_mask & 0x00000001) {
			$cidr = $bit_num unless $cidr;
		    } elsif ($cidr) {
			$error .= " Invalid netmask specification (non-contiguous).\n";
			last;
		    }
		    $int_mask = $int_mask >> 1;
		}
	    }
	}
    }
    if ($error) {
	return $error;
    } elsif (defined($cidr_ref) && !$$cidr_ref) {
	#
	# Pass back the computed CIDR size from the netmask that was passed in.
	#
	$$cidr_ref = $cidr;
	return unless defined($network_ref);
    }

    #
    # So far, each of the passed parameters is syntactically valid.
    # If a network specification was passed, we also have a valid CIDR
    # size which was passed directly or computed from a passed netmask.
    # Now it's time to check if the network/CIDR combination is logically
    # valid.  We'll start by checking for some very general formatting errors.
    #

    if (defined($network_ref)) {
	if ($cidr <= 8) {
	    if (($num_octets >= 2 && $octet_2 != 0) ||
		($num_octets >= 3 && $octet_3 != 0) ||
		($num_octets == 4 && $octet_4 != 0)) {
		$error  = " CIDR sizes /1-8 require just the first non-zero network octet to be specified.\n";
		$error .= " If other octets are included, they must be zeros.\n";
	    } else {
		$network = $rightmost_octet = $octet_1;
		$octet = "first";
	    }

	} elsif ($cidr <= 16) {
	    if ($num_octets < 2) {
		$error = " CIDR sizes /9-16 require the first two network octets to be specified.\n";
	    } elsif (($num_octets >= 3 && $octet_3 != 0) ||
		     ($num_octets == 4 && $octet_4 != 0)) {
		$error  = " CIDR sizes /9-16 require just the first two network octets to be specified.\n";
		$error .= " If other octets are included, they must be zeros.\n";
	    } else {
		$network = "$octet_1.$octet_2";
		$rightmost_octet = $octet_2;
		$octet = "second";
	    }

	} elsif ($cidr <= 24) {
	    if ($num_octets < 3) {
		$error = " CIDR sizes /17-24 require the first three network octets to be specified.\n";
	    } elsif ($num_octets == 4 && $octet_4 != 0) {
		$error  = " CIDR sizes /17-24 require just the first three network octets to be specified.\n";
		$error .= " If the fourth octet is included, it must be zero.\n";
	    } else {
		$network = "$octet_1.$octet_2.$octet_3";
		$rightmost_octet = $octet_3;
		$octet = "third";
	    }

	} elsif ($cidr <= 32) {
	    if ($num_octets < 4) {
		$error = " CIDR sizes /25-32 require all four network octets to be specified.\n";
	    } else {
		$rightmost_octet = $octet_4;
		$octet = "fourth";
	    }
	}
	return $error if $error;

	#
	# Finally, if the CIDR size is not 8, 16, 24, or 32, the rightmost
	# non-zero octet of a passed network specification must be evenly
	# divisible by the appropriate power of two.  Make sure this is so.
	#

	$remainder = $cidr % 8;
	if ($remainder) {
	    $factor = 256 >> $remainder;	# right-shift by the needed bits
	    #
	    # For "$remainder" of   1,  2,  3,  4, 5, 6, or 7
	    #        "$factor" is 128, 64, 32, 16, 8, 4, or 2
	    #
	    if ($rightmost_octet % $factor) {
		$error  = " Invalid network specification for CIDR size of /$cidr";
		$error .= ($$mask_ref) ? " (from subnetmask).\n" : ".\n";
		$error .= " The $octet octet must be evenly divisible by $factor.\n";
		return $error;
	    }
	}
	$$network_ref = $network;
    }

    unless ($$mask_ref) {
	#
	# Compute the netmask from the CIDR size.
	#
	$mask = ((2 ** $cidr) - 1) << (32 - $cidr);
	$octet_1 = ($mask & 0xff000000) >> 24;
	$octet_2 = ($mask & 0x00ff0000) >> 16;
	$octet_3 = ($mask & 0x0000ff00) >> 8;
	$octet_4 =  $mask & 0x000000ff;
	$$mask_ref = "$octet_1.$octet_2.$octet_3.$octet_4";
    }
    return;
}


#
# For CIDR sizes /8 to /24, calculate all of the constituent
# class A, B, or C subnets for a given network and return
# them to the caller as a list.
#
# For CIDR sizes /25 to /32, calculate the range of addresses
# for use in the default naming scheme of the sub-class-C DB file.
# Unless overridden by a `-n x.x.x.x  domain=zone-name' option,
# the default zone file name will also serve as the basis for the
# zone name itself.
#
sub SUBNETS {
    my ($network, $cidr_size) = @_;
    my ($additional_units, $base_net, $final_value, @subnets);

    if ($cidr_size <= 24) {
	push(@subnets, $network);	# the network itself is always first
	return @subnets unless $cidr_size % 8;
	$base_net = $last_octet = $network;
	$base_net =~ s/\.\d+$//;
	$last_octet =~ s/.+\.//;
	if ($cidr_size < 16) {
	    $additional_units = (2 ** (16 - $cidr_size)) - 1;
	} else {
	    $additional_units = (2 ** (24 - $cidr_size)) - 1;
	}
	for (1 .. $additional_units) {
	    $last_octet++;
	    push(@subnets, "$base_net.$last_octet");
	}
    } else {
	#
	# For CIDR sizes 25 to 32, a single array element will
	# be returned in the following format:
	#
	#   class-C_subnet:first_host_address-last_host_address
	#     ^^^^^^^^^^      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	#     lookup key      host address or address range 
	#
	# Examples:  192.6.19.0/25     => 192.6.19:0-127
	#                                 db.192.6.19.0-127
	#            156.153.254.80/28 => 156.153.254:80-95
	#                                 db.156.153.254.80-95
	#
	# For a CIDR size of 32, the address range is simply
	# replaced by the host address number itself, e.g.,
	#
	#            192.6.19.97/32    => 192.6.19:97
	#                                 db.192.6.19.97
	#
	$base_net = $last_octet = $network;
	$base_net =~ s/\.\d+$//;
	$last_octet =~ s/.+\.//;
	if ($cidr_size == 32) {
	    push(@subnets, "$base_net:$last_octet");
	} else {
	    $additional_units = (2 ** (32 - $cidr_size)) - 1;
	    $final_value = $last_octet + $additional_units;
	    push(@subnets, "$base_net:$last_octet-$final_value");
	}
    }
    return @subnets;
}


#
# Subroutine for normalizing various data items once a complete
# view of the operating environment is obtained from PARSE_ARGS.
#
# Normal mode:
# ------------
# * Establish what we will be using for the SOA MNAME and RNAME
#   fields (-h/-u options) and the global MX RRset (-m option)
#   as well as the various NS RRsets (-s/-S options).
#   Single-label domain names will be qualified with the current
#   domain (-d option).
# * Validate any zone apex records submitted via the -T option.
#   NOTE: Error/warning messages will be generated for any invalid
#         domain name and/or SOA RR field.  A go/no-go decision for
#         proceeding will be made depending on the severity of the
#         problems and the error checking level set by the -I option.
# * Set the default mode for handling multi-homed hosts.
# * Optimize the search arrays for the -e, -c, and -p options to
#   accommodate super- and subdomains of related domain trees.
# * Try to query the MNAME host to see what version of BIND is running
#   so that our RFC-2308 status can be unequivocally set.  This also
#   helps to determine whether symbolic TTL values can be used.
#
# Normal mode and Verify mode:
# ---------------------------
# * Get the version of DiG that is available.  This allows the `h2n'
#   program to make various parsing and buffer-size adjustments.
#
sub FIXUP {
    my ($Apex_RRs_aliases, $Fixup_DefAction, $alias, $data, $domain_part);
    my ($epat, $file, $i, $lc_alias, $major_version, $minor_version, $mxhost);
    my ($patch_version, $preference, $rdata, $rrtype, $status, $s, $t);
    my ($tmp_rfc952, $tmp_rfc1123, $tmp_verbose, $ttl, $user_part);
    my (%ServerName, @UniqueServers, @sorted, @temp, @unsorted);

    unless ($verify_mode) {
	if ($preserve_case) {
	    #
	    # Use the CPAN module `Tie::CPHash' [already loaded when the -P
	    # option was encountered in PARSE_ARGS()] to enhance the following
	    # hashes so that their keys can be matched in a case-insensitive
	    # manner while still preserving the case of the last-stored key as
	    # returned by the each() and keys() functions.
	    # NOTE: This added functionality comes at the expense of a fair
	    #       amount of overhead according to the module's documentation.
	    #       Thus, only the necessary hashes are specified.
	    #
	    tie %Aliases,     "Tie::CPHash";
	    tie %AliasesPTR,  "Tie::CPHash";
	    tie %Comments,    "Tie::CPHash";
	    tie %Hosts,	      "Tie::CPHash";
	    tie %HostsPTR,    "Tie::CPHash";
	    tie %Ttl,	      "Tie::CPHash";
	    tie %cAliases,    "Tie::CPHash";
	    tie %deferredPTR, "Tie::CPHash";
	    tie %pendingPTR,  "Tie::CPHash";
	    tie %RRowners,    "Tie::CPHash";
	    tie %Wildcards,   "Tie::CPHash";
	}
	$status = 0;
	$action = "";
	$tmp_rfc952  = $rfc952;
	$tmp_rfc1123 = $rfc1123;
	$rfc952 = 0;
	$rfc1123 = 1;		# SOA, NS, and MX RRs should be RFC-compliant
	$Fixup_DefAction = ($DefAction eq "Warning") ? "Warning" : "Error";
	$Domain =~ s/\.$//;
	#
	# Although the owner fields of SOA records, i.e., delegated domain
	# names, are not subject to RFC-1123 name checking, the fact that
	# this name will become part of the FQDN of A records generated
	# by this program means that the higher standard must be applied.
	#
	$error = &CHECK_NAME($Domain, 'A');
	if ($error) {
	    $status = $error;
	    $action = ($error == 3) ? "Error" : $Fixup_DefAction;
	    print STDERR "$action: Domain name `$Domain' (-d) is invalid.\n";
	}

	if ($Host eq '.' || $Host eq '@') {
	    #
	    # Even though it is clear from the spirit of the RFCs that
	    # the SOA MNAME field should contain the zone's master
	    # name server, BIND does allow the root zone to appear as
	    # a placeholder.  Accommodate this possibility as an
	    # undocumented feature.  Also allow the "@" symbol which,
	    # in this context, represents the zone name of the -d option.
	    # Don't leave the special "@" symbol as-is, however, since
	    # its context will be incorrect in any reverse-mapping zones.
	    #
	    $RespHost = ($Host eq '@') ? $Domain : $Host;
	} else {
	    $Host =~ s/\.$//;
	    $error = &CHECK_NAME($Host, 'A');
	    if ($error) {
		$status = $error if $error > $status;
		$action = ($error == 3) ? "Error" : $Fixup_DefAction;
		print STDERR "$action: SOA host name `$Host' (-h) is invalid.\n";
	    }
	    if ($Host =~ /\./) {
		$RespHost = "$Host.";
	    } else {
		$RespHost = "$Host.$Domain.";
	    }
	    $RespHost =~ s/\.\././g;		# remove redundant "."
	}

	# As of BIND 4.9.4, RNAMEs in SOA and RP can have any printable
	# character in the first label as long as the subsequent labels
	# form an RFC1123-compliant domain name.  Since this has not been
	# make official in any RFC subsequent to RFC-1035, the preference
	# of the `h2n' program is to stick with the original specification
	# of interpreting the first unescaped "." as the implied "@" character.
	#
	if ($User =~ /@/) {
	    $User =~ s/\\@/@/;			    # unescape the "@"
	    ($user_part, $domain_part) = split(/@/, $User);
	    $user_part =~ s/\./\\./g;		    # escape "." in username
	    1 while $user_part =~ s/\\\\/\\/g;	    # remove redundancies
	    if ($domain_part =~ /\./) {		    # multiple domain labels
		$domain_part .= ".";		    # append root domain
	    } else {
		$domain_part .= ".$Domain.";	    # append our domain name
	    }
	    $RespUser = "$user_part.$domain_part";  # join w/ unescaped "."
	} elsif ($User !~ /\.$/) {		    # already RNAME if no
	    $user_part = $User;			    # "@" and trailing "."
	    $user_part =~ s/\./\\./g;		    # escape "." in username
	    1 while $user_part =~ s/\\\\/\\/g;	    # remove redundancies
	    $RespUser = "$user_part.$Domain.";	    # join w/ unescaped "."
	} else {
	    $RespUser = $User;
	}
	$RespUser =~ s/\.\././g;
	#
	# Final inspection.
	#
	if ($RespUser ne ".") {
	    $tmp = $RespUser;
	    $tmp =~ s/(\\[.]|[^.])*\.//;	# strip first label
	    $tmp =~ s/\.$//;			# strip last dot
	    unless ($tmp) {
		$status = 3;			# flag this as a fatal error
		print STDERR "Error: SOA RNAME field `$RespUser' (-u) is invalid.\n";
	    } else {
		$error = &CHECK_NAME($tmp, 'A');
		if ($error) {
		    $status = $error if $error > $status;
		    $action = ($error == 3) ? "Error" : $Fixup_DefAction;
		    print STDERR "$action: SOA RNAME field `$RespUser' (-u) is invalid.\n";
		}
	    }
	}

	# Clean up name servers.
	#
	foreach $s (@FullServers) {
	    $s =~ s/\.$//;
	    $s = $Domain if $s eq '@';
	    $error = ($s) ? &CHECK_NAME($s, 'A') : 0;
	    if ($error) {
		$status = $error if $error > $status;
		$action = ($error == 3) ? "Error" : $Fixup_DefAction;
		print STDERR "$action: Name server name `$s' (-s) is invalid.\n";
	    }
	    $s .= ".$Domain" unless $s =~ /\./;
	    $s .= ".";
	    if ($action ne "Error" && exists($ServerName{lc($s)})) {
		print STDERR "Ignoring redundant name server (-s): $s\n";
	    } else {
		push(@UniqueServers, $s);
		$ServerName{lc($s)} = 1;
	    }
	}
	@FullServers = @UniqueServers;

	foreach $t (keys %PartialServers) {
	    @temp = split(' ', $PartialServers{$t});
	    @UniqueServers = ();
	    %ServerName = ();
	    foreach $s (@temp) {
		$s =~ s/\.$//;
		$s = $Domain if $s eq '@';
		$error = ($s) ? &CHECK_NAME($s, 'A') : 0;
		if ($error) {
		    $status = $error if $error > $status;
		    $action = ($error == 3) ? "Error" : $Fixup_DefAction;
		    print STDERR "$action: Name server name `$s' (-S) is invalid.\n";
		}
		$s .= ".$Domain" unless $s =~ /\./;
		$s .= ".";
		if ($action ne "Error" && exists($ServerName{lc($s)})) {
		    print STDERR "Ignoring redundant name server (-S): $s\n";
		} else {
		    push(@UniqueServers, $s);
		    $ServerName{lc($s)} = 1;
		}
	    }
	    $PartialServers{$t} = join(' ', @UniqueServers);
	}

	unless ($do_mx || $do_zone_apex_mx) {
	    if (@MX) {
		print STDERR "Warning: The global MX record(s) specified with the -m option(s) will\n";
		print STDERR "         not be generated due to the -M option also being specified.\n";
	    }
	} elsif ($do_zone_apex_mx && !@MX) {
	    print STDERR "Warning: The `-T mode=M' option is effectively cancelled due to the fact\n";
	    print STDERR "         that no global MX records were specified with the -m option.\n";
	    $do_zone_apex_mx = 0;
	} elsif (@MX) {
	    #
	    # Clean up MX hosts.
	    #
	    %ServerName = ();
	    foreach $s (@MX) {
		($preference, $mxhost) = split(' ', $s, 2);
		$mxhost =~ s/\.$//;
		$data = ($mxhost eq '@') ? $Domain : $mxhost;
		$error = ($data) ? &CHECK_NAME($data, 'MX') : 0;
		if ($error) {
		    $status = $error if $error > $status;
		    $action = ($error == 3) ? "Error" : $Fixup_DefAction;
		    print STDERR "$action: MX hostname `$mxhost' (-m) is invalid.\n";
		}
		if ($mxhost =~ /$Domainpattern$/) {
		    #
		    # Prevent unnecessary verbosity by keeping in-domain
		    # names in origin-relative format since the MX records
		    # will appear only in the forward-mapping file.
		    #
		    $mxhost =~ s/$Domainpattern$//;
		} else {
		    $mxhost .= "." if $mxhost =~ /\./;
		}
		$s = "$preference $mxhost";
		$mxhost = ($mxhost eq '@') ? lc("$Domain.") : lc($mxhost);
		if (exists($ServerName{$mxhost})) {
		    $ServerName{$mxhost} .= " $preference";
		} else {
		    $ServerName{$mxhost} = $preference;
		}
	    }
	    unless ($action eq "Error") {
		#
		# Now go through the hash of MX hosts.  Those that are
		# redundantly specified will have multiple preference
		# values.  Keep the most preferred entry, i.e., the
		# preference value that is numerically smallest.
		#
		scalar(keys(%ServerName));
		while (($mxhost, $data) = each %ServerName) {
		    unless ($data =~ /^\d+$/) {
			@temp = sort { $a <=> $b } split(' ', $data);
			$ServerName{$mxhost} = $temp[0];
		    }
		}
		@UniqueServers = ();
		foreach $s (@MX) {
		    ($preference, $mxhost) = split(' ', $s, 2);
		    $mxhost = ($mxhost eq '@') ? lc("$Domain.") : lc($mxhost);
		    unless (exists($ServerName{$mxhost})
			    && $preference == $ServerName{$mxhost}) {
			print STDERR "Ignoring redundant MX host (-m): $s\n";
		    } else {
			push(@UniqueServers, $s);
			delete($ServerName{$mxhost});
		    }
		}
		@MX = @UniqueServers;
	    }
	}

	# Validate and register any zone apex RRs or aliases that were
	# configured with the -T option.  This is done by writing the
	# RRs to a temporary file and submitting the data to READ_RRs().
	#
	$Apex_RRs_aliases = (keys(%Apex_RRs) || keys(%Apex_aliases)) ? 1 : 0;
	if ($Apex_RRs_aliases || $do_zone_apex_mx) {
	    if ($Apex_RRs_aliases) {
		if ($verbose) {
		    print STDOUT "Checking zone apex RRs (-T option)...\n";
		}
		$file = "$debug_DIR/-T_option_RRs";
		unless (open(*APEXRRS, "> $file")) {
		    print STDERR "Couldn't create temporary file for validating RRs entered with the -T option.\nError: $!\n";
		    print STDERR "I give up ... sorry.\n";
		    exit(2);
		}
	    }
	    if (keys(%Apex_RRs)) {
		print APEXRRS "\@";
		foreach $rrtype (keys %Apex_RRs) {
		    foreach $data (@{ $Apex_RRs{$rrtype} }) {
			$rdata = $data;
			if ($rdata =~ /\n$/) {
			    #
			    # A newline appended to the "$rdata" string is a
			    # data structure signal to indicate that this is
			    # a continuation line of a multi-line record.
			    #
			    $rdata =~ s/\n$//;
			    if ($rdata =~ /\n$/) {
				#
				# Besides this being a continuation line,
				# a second appended newline signifies that
				# the previous line ended with an open quote
				# in effect.  Therefore, the usual cosmetic
				# indentation must not be added in order to
				# maintain data integrity.
				#
				print APEXRRS $rdata;
			    } else {
				print APEXRRS "\t\t\t$rdata\n";
			    }
			} else {
			    ($ttl, $rdata) = split(/,/, $rdata, 2);
			    print APEXRRS "\t$ttl\t$rrtype\t$rdata\n";
			}
		    }
		}
	    }
	    if (keys(%Apex_aliases)) {
		#
		# Add any CNAMEs that were configured with the -T option.
		# These always point to the zone apex.
		#
		while (($lc_alias, $alias) = each %Apex_aliases) {
		    ($alias, $ttl) = split(' ', $alias, 2);
		    if ($alias =~ /$Domainpattern\.$/) {
			#
			# Prevent unnecessary verbosity by keeping in-domain
			# names in origin-relative format.
			#
			$alias =~ s/$Domainpattern\.$//;
			$Apex_aliases{$lc_alias} = "$alias $ttl";
		    }
		    printf APEXRRS "%s%s\tCNAME\t\@\n", &TAB($alias, 16), $ttl;
		}
	    }
	    if ($Apex_RRs_aliases) {
		close(*APEXRRS);
		$tmp_verbose = $verbose;
		$verbose = 1;
		$newline_printed = READ_RRs($file, "$Domain.", "$Domain.",
					    "$Domain.", 0);
		$verbose = $tmp_verbose;
		$status = $Load_Status if $Load_Status > $status;
		unlink($file) unless $debug;
	    }
	    if ((($status == 2 && $Fixup_DefAction eq "Warning") || $status < 2)
		&& $do_zone_apex_mx) {
		#
		# Add the global MX records from the -m option to any
		# that were additionally specified for the zone apex
		# and report any redundancies between the two sets.
		#
		foreach $s (@MX) {
		    ($preference, $mxhost) = split(' ', $s, 2);
		    $mxhost = ($mxhost eq '@') ? lc("$Domain.") : lc($mxhost);
		    if (exists($Apex_route_RRs{MX})
			&& exists($Apex_route_RRs{MX}{$mxhost})) {
			print STDERR "Redundant MX hostname; -T/-m options\n";
			print STDERR " @\tMX\t$s\n";
		    }
		    # Prepend "," as the placeholder for the default TTL.
		    #
		    push(@{ $Apex_RRs{MX} }, ",$s");
		}
	    }
	    if ($Apex_RRs_aliases) {
		print STDERR "\n" while $newline_printed--;
	    }
	}

	if ($status > 1) {
	    if (($status == 2 && $Fixup_DefAction eq "Error") || $status > 2) {
		print STDERR "I give up ... sorry.\n";
		exit(2);
	    } else {
		($message = <<EOT) =~ s/^\s+\|//gm;
    |Attention! Because `h2n' is running with an error-checking level of
    |           `$check_level' (-I option), it will go ahead and process the
    |           host table despite the above warning(s).  It is very important,
    |           however, to have fully-compliant SOA, NS, and MX records in
    |           order to prevent interoperability issues with other name servers
    |           and/or mail servers.  These naming irregularities should be
    |           fixed at the earliest opportunity.
EOT
		print STDERR "$message\n";
	    }
	}
	$rfc952  = $tmp_rfc952;
	$rfc1123 = $tmp_rfc1123;

	# If no +m option was specified, forward and reverse RRsets of
	# multi-homed hosts will be generated in the default manner.
	#
	$multi_homed_mode = "D" unless $multi_homed_mode;

	# Optimize the search arrays for the -c, -p, and -e options.
	# 
	for $i (1..2) {
	    @temp = ($i == 1) ? @cpats : @ptrpats;
	    next unless @temp;
	    #
	    # If a domain and one or more of its subdomains were specified
	    # in a -c or -p option, they should be ordered from the most
	    # specific subdomain to the least specific superdomain, e.g.,
	    # `fx.movie.edu' should be tried for a matching host file entry
	    # before `movie.edu'.  Reversing the domain name labels, sorting
	    # them in descending order, and reversing the labels back again
	    # will accomplish this task.
	    # 
	    @unsorted = ();
	    foreach $tmp (@temp) {
		if ($Domain =~ /\.$tmp$/i) {
		    #
		    # The domain of a -c or -p option is a parent domain
		    # of that specified in the -d option.  Add a special
		    # flag to allow a host in the -d domain to override
		    # the -c or -p processing that would otherwise occur.
		    #
		    if ($i == 1) {
			$cModeSpec{$tmp} .= "O";
		    } else {
			$pModeSpec{$tmp} .= "O";
		    }
		}
		($domain_part = $tmp) =~ s/\\[.]/./g;
		push(@unsorted, join('.', reverse(split('\.', $domain_part))));
	    }
	    @sorted = sort { $b cmp $a } @unsorted;
	    foreach $tmp (@sorted) {
		$tmp = join('.', reverse(split('\.', $tmp)));
		$tmp =~ s/\./\\./g;
	    }
	    if ($i == 1) {
		@cpats = @sorted;
	    } else {
		@ptrpats = @sorted;
	    }
	}
	if (@elimpats >= 2) {
	    #
	    # If a domain and one or more of its subdomains were specified
	    # in a -e option, the subdomains are effectively redundant,
	    # e.g., `movie.edu' matches everything that `fx.movie.edu'
	    # matches.  All such subdomains will now be removed.
	    #
	    @unsorted = ();
	    foreach $epat (@elimpats) {
		($domain_part = ".$epat") =~ s/\\[.]/./g;
		push(@unsorted, join('.', reverse(split('\.', $domain_part))));
	    }
	    @sorted = sort { $a cmp $b } @unsorted;  # ascending order this time
	    $i = 0;
	    until ($i >= $#sorted) {
		($domain_part = $sorted[$i]) =~ s/\./\\./g;
		while ($i < $#sorted && $sorted[$i + 1] =~ /^$domain_part/) {
		    #
		    # Remove the array element that holds the unnecessary
		    # subdomain or duplicate domain name.
		    #
		    splice(@sorted, ($i + 1), 1);
		}
		$i++;
	    }
	    if (@sorted < @elimpats) {
		foreach $epat (@sorted) {
		    $epat =~ s/\.$//;
		    $epat = join('.', reverse(split('\.', $epat)));
		    $epat =~ s/\./\\./g;
		}
		@elimpats = @sorted;
	    }
	}
	if (@elimpats) {
	    #
	    # Look for any -e domains that happen to be parent domains
	    # of those specified in the -d option or -c or -p options.
	    # Construct the appropriate exception domain patterns in
	    # the "%ePatExceptions" hash.
	    #
	    foreach $epat (@elimpats) {
		@unsorted = ();
		if ($Domain =~ /\.$epat$/i) {
		    push(@unsorted, join('.', reverse(split('\.', ".$Domain"))));
		}
		foreach $tmp (@cpats) {
		    ($domain_part = ".$tmp") =~ s/\\[.]/./g;
		    if ($domain_part =~ /\.$epat$/) {
			push(@unsorted, join('.', reverse(split('\.', $domain_part))));
		    }
		}
		foreach $tmp (@ptrpats) {
		    ($domain_part = ".$tmp") =~ s/\\[.]/./g;
		    if ($domain_part =~ /\.$epat$/) {
			push(@unsorted, join('.', reverse(split('\.', $domain_part))));
		    }
		}
		unless (@unsorted) {
		    $ePatExceptions{$epat} = '^$';
		} else {
		    #
		    # Just as was done previously when effectively redundant
		    # subdomains were removed from the -e option, compress
		    # the list of exception domains down to the minimum set
		    # of matching highest-level domains.
		    #
		    @sorted = sort { $a cmp $b } @unsorted;
		    $i = 0;
		    until ($i >= $#sorted) {
			($domain_part = $sorted[$i]) =~ s/\./\\./g;
			while ($i < $#sorted && $sorted[$i + 1] =~ /^$domain_part/) {
			    splice(@sorted, ($i + 1), 1);
			}
			$i++;
		    }
		    $domain_part = "";
		    foreach $tmp (@sorted) {
			$tmp = "." . join('.', reverse(split('\.', $tmp)));
			$tmp =~ s/\./\\./g;
			$domain_part .= "|$tmp";
		    }
		    $domain_part =~ s/^\|//;
		    $ePatExceptions{$epat} = $domain_part;
		}
	    }
	}
    }

    unless (defined($show_nxdomain_cnames)) {
	if ($verify_mode && $Vdomains[0] =~ /\.in-addr\.arpa\.$/) {
	    $show_nxdomain_cnames = 0;
	} else {
	    $show_nxdomain_cnames = 1;
	}
    }
    # `h2n' will try to call the DiG utility to provide various
    # items of useful DNS information.  Get the version of DiG that
    # is installed on this system in order to accommodate the various
    # differences in which its output may appear.
    #
    $DiG_version_num = 0;
    if (open(*DIGOUT, "$DiG 1.0.0.127.in-addr.arpa. PTR 2>&1 |")) {
	while (<DIGOUT>) {
	    if (/^; <<>> DiG (\d+)(\.(\d+))?(\.(\d+).*)? <<>>/) {
		$major_version = (defined $1) ? $1 : 0;
		$minor_version = (defined $3) ? $3 : 0;
		$patch_version = (defined $5) ? $5 : 0;
		$DiG_version_num = (10000 * $major_version)
				   + (100 * $minor_version) + $patch_version;
		last;
	    }
	}
	close(*DIGOUT);

	$DiG_timeout = "time=$DiG_timeout";
	if ($DiG_version_num < 80300) {
	    #
	    # Set the threshold at which long command lines to DiG
	    # must be split across two lines when following chained
	    # CNAMEs in the AUDIT_RRs subroutine.
	    #
	    $DiG_bufsize = 98;
	    $DiG_retries = "retry=$DiG_retries";
	} elsif ($DiG_version_num < 90000) {
	    #
	    # The buffer size was increased in version 8.3.  Assume
	    # that the same value exists for subsequent 8.X versions.
	    #
	    $DiG_bufsize = 382;
	    $DiG_retries = "retry=$DiG_retries";
	} else {
	    #
	    # Version 9 of DiG is a rewrite of this utility with a
	    # significant increase in the buffer sizes.
	    #
	    $DiG_bufsize = 986;
	    $DiG_retries = "tries=$DiG_retries";
	}
    }

    unless ($verify_mode) {
	#
	# RFC-2308 is implemented in BIND name servers starting with
	# version 8.2.  This specifies that the SOA Minimum Field
	# is defined to be the negative caching interval and the
	# default time-to-live value is now defined with a new
	# master zone file directive, $TTL.
	# BIND version 8.2 and later will issue a warning when the
	# $TTL directive is missing from a master zone that is being
	# loaded.  BIND 9 versions prior to 9.2.0a1 will fail to load
	# master zones which are missing the $TTL directive.
	#
	# In order to suppress these warnings/errors, we'll try to
	# use DiG to issue a special query to the master name server
	# to find out which version of BIND it is running.
	# Knowing the BIND version allows an unambiguous determination
	# of our RFC-2308 status according to the following hierarchy:
	#
	#   (1) BIND version
	#          |
	#          |  overrides
	#          |
	#   (2) confirmed RFC-2308 status via a +t option
	#          OR
	#       discovered $TTL directives in existing DB files
	#          |
	#          |  overrides
	#          |
	#   (3) negated RFC-2308 status via a -o option with
	#       exactly four positional arguments
	#          |
	#          |  overrides
	#          |
	#   (4) default RFC-2308 status of "true" for `h2n'
	#       version 2.40 and later
	#          |
	#          |  obsoletes
	#          |
	#   (5) default RFC-2308 status of "false" for `h2n'
	#       version 2.39 and earlier.
	#      
	# Also, knowing the working BIND version determines how to
	# handle TTL values.  Specifically, symbolic TTL values
	# were not supported until version 8.2.1.  If our version
	# of BIND is an earlier one or can not be determined, all
	# TTL values will be converted into their equivalent number
	# of seconds.
	#
        $Host = ($RespHost eq ".") ? "127.0.0.1" : $RespHost;
	&GET_BIND_VERSION($Host);
	if ($BIND_version_num) {
	    if ($BIND_version_num < 80200) {
		#
		# Disable the generation of $TTL directives in the MAKE_SOA
		# subroutine by negating the "$rfc2308" flag to the "hard"
		# value of 0 regardless of what was specified in a -o/+t
		# option.  $TTL directives in existing DB files will be
		# effectively removed.
		#
		$rfc2308 = 0;

		# NOTE: If a value for "$MasterTtl" was specified with a
		#       -o/+t option, transfer the value to its proper
		#       context in the SOA MINIMUM field, "$Ttl".
		#       Otherwise, a -o "$Ttl" argument will take effect
		#       in the MAKE_SOA subroutine or, if no "$Ttl" was
		#       specified, the value of "$DefTtl" will take effect.
		#       In either case, any RFC-2308 Negative Caching value
		#       that may have been specified with -o/+t is cancelled.
		# 
		$Ttl = $MasterTtl if $MasterTtl;
	    } else {
		#
		# The detected BIND version implements RFC-2308 and so $TTL
		# directives must always be present in every zone file.
		#
		unless ($rfc2308) {
		    #
		    # RFC-2308 status was toggled off by a -o option that
		    # specified exactly four arguments.  If a value for
		    # "$Ttl" was specified, transfer the value to its
		    # proper context in "$MasterTtl".  Otherwise, existing
		    # $TTL directives will retain their values or be created
		    # with the value of "$DefTtl".
		    # 
		    $MasterTtl = $Ttl if $Ttl;

		    # The MAKE_SOA subroutine will use the SOA time intervals
		    # from an already-existing DB file in the absence of 
		    # replacement values via a -o/+t option.  Since a positive
		    # RFC-2308 status from the detected BIND version overrides
		    # the negated status set by the -o option, it is best to
		    # make the following assumptions:
		    #
		    #   1. The name server was upgraded to BIND 8.2 or newer
		    #      and the -o option was not updated to reflect this.
		    #
		    #   2. The existing DB files still have SOA RRs with the
		    #      SOA Minimum fields in their old context of holding
		    #      a positive TTL value.
		    #
		    # In order to make sure that existing SOA Minimum fields
		    # assume their new context, we'll explicitly set "$Ttl"
		    # as though a value had been passed via the -o/+t option
		    # in an RFC-2308 context.  The SOA RRs will thus be
		    # initialized with the default recommended value for the
		    # negative cache interval.
		    #
		    $Ttl = $DefNegCache;
		}
		# Make sure the generation of $TTL directives is unequivocally
		# forced by setting the "$rfc2308" flag to a "hard" value of 2.
		#
		$rfc2308 = 2;
	    }
	} else {
	    #
	    # The version of BIND running on the master name server (-h option)
	    # can not be determined.  Either the BIND daemon is not running or
	    # the version ID may have been altered in the configuration file
	    # or an altered version of the special RR `version.bind.   CH TXT'
	    # may have been created.
	    #
	    if ($rfc2308) {
		#
		# Either no -o/+t option was specified ("$rfc2308" starts out
		# with a default value of 1 at the start of the program), a
		# -o option did not toggle the RFC-2308 status to false, and/or
		# a +t option forced RFC-2308 to be assumed.
		# Make sure the generation of $TTL directives is unequivocally
		# forced by setting the "$rfc2308" flag to a "hard" value of 2.
		#
		$rfc2308 = 2;
	    } else {
		#
		# RFC-2308 status was toggled off by a -o option that
		# specified exactly four arguments.  Even though we don't
		# know which version of BIND we're running, our RFC-2308
		# override policy is that an existing $TTL directive which
		# is discovered by the MAKE_SOA subroutine will toggle the
		# RFC-2308 status back to true.
		# Allow this possible change to take place by setting the
		# "$rfc2308" flag to the "soft" value of 1.
		#
		$rfc2308 = 1;
	    }
	}
    }
}


#
# Subroutine for obtaining the version string of a BIND name server.
# The following global variables are set:
#
#   $BIND_version     : Actual version string
#   $BIND_version_num : Numerical version value for use in comparisons
#   $BIND_ver_msg     : List of bugs which the BIND version may have
#
sub GET_BIND_VERSION {
    my ($nameserver) = @_;
    my ($BIND48_bug_tokens, $BIND_bugs, $CERT_URL_refs, $continuation_line);
    my ($major_version, $minor_version, $patch_version, $query_options);
    my ($release_token, $status, $tmp_version);
    local *DIGOUT;

    $BIND48_bug_tokens = $BIND_bugs = $BIND_ver_msg = $CERT_URL_refs
		       = $continuation_line = $status = "";
    $BIND_version_num = 0;
    if (defined($debug_BIND_version)) {
	$BIND_version = $debug_BIND_version;
    } else {
	$BIND_version = "unavailable";
	return if $DiG_version_num == 0;
	$query_options = "+nostats +$DiG_timeout +$DiG_retries"
			 . " \@$nameserver version.bind txt chaos";
	open(*DIGOUT, "$DiG $query_options 2>&1 |");
	while (<DIGOUT>) {
	    chop;
	    next if /^$/;
	    if (/^;.+HEADER.+opcode: QUERY, status: ([^,]+)/) {
		$status = $1;
		if ($status ne 'NOERROR') {
		    if ($status =~ /^(NOTIMPL?|FORMERR)$/) {
			#
			# We are probably dealing with a non-BIND name server.
			# Microsoft -> NOTIMP
			# djbdns    -> FORMERR
			#
			$BIND_version = "*Non-BIND NS*";
		    } else {
			$BIND_version = $status;
		    }
		    last;
		}
	    } elsif (/^VERSION\.BIND\.\s+.*TXT\s+\"([^\"]*)/i) {
		$BIND_version = $1;
		if ($BIND_version =~ /\\$/) {
		    $BIND_version =~ s/(\s+)\\$/$1/;  # remove pre-spaced escape
		    $BIND_version =~ s/\\$/ /;	      # escape -> space char.
		    $continuation_line = "1";
		    next;
		} else {
		    $BIND_version = "unavailable" if $BIND_version =~ /^\s*$/;
		    last;
		}
	    } elsif ($continuation_line && /([^\"]*)/) {
		$continuation_line = $1;
		$continuation_line =~ s/^\s+//;
		$BIND_version .= $continuation_line;
		if ($BIND_version =~ /\\$/) {
		    $BIND_version =~ s/(\s+)\\$/$1/;
		    $BIND_version =~ s/\\$/ /;
		    next;
		} else {
		    $BIND_version = "unavailable" if $BIND_version =~ /^\s*$/;
		    last;
		}
	    }
	}
	close(*DIGOUT);
	return unless $status eq "NOERROR";
    }

    if ($BIND_version =~ /^(\d+)\.(\d+)(\.(\d+))?(.*)/) {
	$major_version = (defined $1) ? $1 : 0;
	$minor_version = (defined $2) ? $2 : 0;
	$patch_version = (defined $4) ? $4 : 0;
	$release_token = (defined $5) ? $5 : "";
	$BIND_version_num = (10000 * $major_version)
			    + (100 * $minor_version) + $patch_version;
    }
    if ($BIND_version_num < 90000 && $BIND_version =~ /[48][.]\d/) {
	#
	# See if "$BIND_version" resembles the format of a standard BIND
	# version string.  If so, we'll massage the text a bit and check
	# "%BIND_bug_index" for any security-related defects to which
	# this version may be vulnerable.
	# NOTE: Here are the samples of customized BIND version
	#       strings that have been seen so far:
	#
	#       "named 8.2.3 for Multinet V4.3 Process Software"
	#	"Meta IP/DNS V4.1 - BIND V8.1.2 (Build 4704 )"
	# 
	$tmp_version = $BIND_version;
	for ($tmp_version) {
	    s/.*(named|BIND) //i;		# remove these prepended titles
	    s/^V(ers?i?o?n ?)?(\d)/$2/i;	# remove custom prepended text
	    s/(\d)[.-]REL.*/$1/i;		# truncate "-RELease"
	    s/(\d)\s*\(.*\)$/$1/;		# remove custom appended text
	    s/(\d)[.-][PT](\d).*/$1-P$2/i;	# standardize Test & Beta vers.
	    s/^8.2.3-P.*/8.2.3-T/;		# standardize 8.2.3-T?? versions
	}
	if ($BIND_version_num == 0
	    && $tmp_version =~ /^(\d+)\.(\d+)(\.(\d+))?/) {
	    #
	    # Now that the customized version string has undergone an
	    # attempted cleanup, make another stab at determining the
	    # equivalent numerical version.
	    #
	    $major_version = (defined $1) ? $1 : 0;
	    $minor_version = (defined $2) ? $2 : 0;
	    $patch_version = (defined $4) ? $4 : 0;
	    $BIND_version_num = (10000 * $major_version)
				+ (100 * $minor_version) + $patch_version;
	}
	if (exists($BIND_bug_index{$tmp_version})) {
	    $BIND48_bug_tokens = $BIND_bug_index{$tmp_version};
	    for ($BIND48_bug_tokens) {
		s/(\S+) /$1, /g;
		s/^([^,]+), ([^,]+)$/$1 & $2/;
		s/, ([^,]+)$/, & $1/;
	    }
	    $CERT_URL_refs = "     $CERT_URL_bugs,\n";
	}
	if (($BIND_version_num >= 40803 && $BIND_version_num < 40909) ||
	    ($BIND_version_num >= 80000 && $BIND_version_num < 80206) ||
	    ($BIND_version_num >= 80300 && $BIND_version_num < 80303)) {
	    $BIND_bugs = "libbind buffer overflow, ";
	    $CERT_URL_refs  .= "     $CERT_URL_libbind,\n";
	}
	if (($BIND_version_num >= 40905 && $BIND_version_num <= 40910) ||
	    ($BIND_version_num >= 80100 && $BIND_version_num <= 80206) ||
	    ($BIND_version_num >= 80300 && $BIND_version_num <= 80303)) {
	    $BIND_bugs .= "BIND: Remote Execution of Code, ";
	}
	if (($BIND_version_num >= 80200 && $BIND_version_num <= 80206) ||
	    ($BIND_version_num >= 80300 && $BIND_version_num <= 80303)) {
	    $BIND_bugs .= "BIND: Multiple Denial of Service, ";
	}
	if ($BIND_version_num >= 40902 && $BIND_version_num <= 40910) {
	    $BIND_bugs .= "LIBRESOLV: buffer overrun, ";
	}
	if ($BIND_bugs =~ /(BIND|LIBRESOLV): /) {
	    $CERT_URL_refs .= "     $CERT_URL_buf_DoS,\n";
	}
    } elsif ($BIND_version_num >= 90000) {
	#
	# BIND 9 version strings have a more standard format than BIND 8.
	#
	if ($BIND_version_num >= 90100 && $BIND_version_num < 90200) {
	    #
	    # These versions of BIND included a vulnerable version
	    # of the OpenSSL library and were automatically linked
	    # to it.  BIND versions 9.2.X and later may be affected
	    # if optionally linked to a vulnerable OpenSSL library
	    # with the `--with-openssl=libpath' configuration option.
	    #
	    $BIND_bugs = "OpenSSL buffer overflow, ";
	    $CERT_URL_refs = "     $CERT_URL_openssl,\n";
	}
	if ($BIND_version_num < 90201 ||
	    ($BIND_version_num == 90201 && $release_token)) {
	    $BIND_bugs .= "DoS internal consistency check, ";
	    $CERT_URL_refs .= "     $CERT_URL_DoS,\n";
	}
	if ($BIND_version_num >= 90200 && $BIND_version_num <= 90201) {
	    #
	    # This release of BIND may be vulnerable to the
	    # `libbind buffer overflow' bug if configured with
	    # the `--enable-libbind' option.
	    #
	    $BIND_bugs .= "libbind buffer overflow, ";
	    $CERT_URL_refs .= "     $CERT_URL_libbind,\n";
	}
    }
    if ($CERT_URL_refs) {
	$CERT_URL_refs =~ s/,\n$//;
	unless ($CERT_URL_refs =~ /,/) {
	    #
	    # Just one CERT advisory to report.
	    #
	    $BIND_bugs =~ s/, $//;
	    $BIND_bugs = $BIND48_bug_tokens . $BIND_bugs;
	    $BIND_ver_msg = " $BIND_bugs.\n"
			  . " See $ISC_URL and\n"
			  . "$CERT_URL_refs for details.";
	} else {
	    if ($BIND48_bug_tokens) {
		$BIND48_bug_tokens =~ s/,? &/,/;
		$BIND48_bug_tokens .= ",\n ";
	    }
	    $BIND_bugs =~ s/([^,]+,[^,]+,)/$1\n/gm;	# Insert newline after
	    $BIND_bugs =~ s/,?. $//s;			# every other entry.
	    $BIND_bugs = $BIND48_bug_tokens . $BIND_bugs;
	    $CERT_URL_refs =~ s/(.+),\n(.+)/$1, and\n$2/s;
	    $BIND_ver_msg = " $BIND_bugs.\n"
			  . " See $ISC_URL,\n"
			  . "$CERT_URL_refs for details.";
	}
    }
    return;
    
}

    
#
# Subroutine to look for a special configuration file that holds the
# network connectivity information for computer running `h2n' and
# other customizable values.  The file search order is:
#
#   $HOME/.h2nrc
#   $PWD/h2n.conf
#   /etc/h2n.conf
#   /etc/opt/h2n/h2n.conf
#   /usr/local/etc/h2n.conf
#
# If found, the data contained therein will replace the built-in values
# of the following global data structures:
#
#   @local_networks
#   @local_subnetmasks
#   $DiG
#   $check_del
#   $DiG_retries
#   $DiG_timeout
#
sub READ_RCFILE {
    my ($CWD, $buffer, $cidr_size, $data, $error, $file, $first_net_token);
    my ($line_num, $mask_size, $netmask, $network, $options_file, $subnet_mask);
    my @conf_paths = (".h2nrc", "./h2n.conf", "/etc/h2n.conf",
		      "/etc/opt/h2n/h2n.conf", "/usr/local/etc/h2n.conf");

    $first_net_token = 1;
    $options_file = "";
    $CWD = getcwd();
    chdir;				# change to the user's $HOME
    foreach $file (@conf_paths) {
	if (-f $file && -r $file && -s $file && open(*CONF, $file)) {
	    $file = "\$HOME/$file" if $file eq ".h2nrc";
	    while (<CONF>) {
		$line_num++;
		chop;
		s/^\s+//;
		next if /^#/ || /^$/;
		s/#.*//;
		s/\s+$//;
		if (/^LOCAL-NETWORKS\s*=/i) {
		    if ($first_net_token) {
			@local_networks = ();
			@local_subnetmasks = ();
			$first_net_token = 0;
		    }
		    s/^LOCAL-NETWORKS\s*=\s*//i;
		    ($buffer = $_) =~ s/,/ /g;
		    while ($buffer) {
			($data, $buffer) = split(' ', $buffer, 2);
			($data, $subnet_mask) = split(/:/, $data, 2);
			$subnet_mask = "" unless defined($subnet_mask);
			($network, $cidr_size) = split(/\//, $data, 2);
			$cidr_size = 0 unless defined($cidr_size);
			if ($data eq "0/0") {
			    $subnet_mask = "0.0.0.0";
			} else {
			    $netmask = undef;
			    $error = &CHECK_NET(\$network, \$cidr_size, \$netmask);
			    if ($error) {
				print STDERR "Ignoring bad LOCAL-NETWORKS value ($data) at line $line_num\nin configuration file `$file':\n";
				print STDERR $error;
				next;
			    } else {
				#
				# Re-initialize "$data" in case the network-
				# portion of the specification was normalized.
				#
				$data = "$network/$cidr_size";
				unless ($subnet_mask) {
				    $subnet_mask = $netmask;
				} else {
				    $mask_size = undef;
				    $error = &CHECK_NET(undef, \$mask_size, \$subnet_mask);
				    if ($error || $mask_size < $cidr_size) {
					print STDERR "Ignoring bad LOCAL-NETWORKS value ($data) at line $line_num\nin configuration file `$file':\n";
					if ($error) {
					    print STDERR $error;
					} else {
					    print STDERR " The number of mask bits is fewer than that of the corresponding network.\n";
					}
					next;
				    }
				}
			    }
			}
			push(@local_networks, $data);
			push(@local_subnetmasks, $subnet_mask);
		    }
		} elsif (/^DIG-UTILITY\s*=/i) {
		    s/^DIG-UTILITY\s*=\s*//i;
		    $DiG = $_;
		    if ($DiG =~ /\//) {
			if (! -e $DiG) {
			    print STDERR "Non-existent file for DIG-UTILITY at line $line_num\nin configuration file `$file'\n";
			    print STDERR "Resetting default value to unqualified filename of `dig'.\n";
			    $DiG = "dig";
			} elsif (! -x $DiG) {
			    print STDERR "Non-executable file for DIG-UTILITY at line $line_num\nin configuration file `$file'\n";
			    print STDERR "Resetting default value to unqualified filename of `dig'.\n";
			    $DiG = "dig";
			}
		    }
		} elsif (/^CHECK_DEL-UTILITY\s*=/i) {
		    s/^CHECK_DEL-UTILITY\s*=\s*//i;
		    $check_del = $_;
		    if ($check_del =~ /\//) {
			if (! -e $check_del) {
			    print STDERR "Non-existent file for CHECK_DEL-UTILITY at line $line_num\nin configuration file `$file'\n";
			    print STDERR "Resetting default value to unqualified filename of `check_del'.\n";
			    $check_del = "check_del";
			} elsif (! -x $check_del) {
			    print STDERR "Non-executable file for CHECK_DEL-UTILITY at line $line_num\nin configuration file `$file'\n";
			    print STDERR "Resetting default value to unqualified filename of `check_del'.\n";
			    $check_del = "check_del";
			}
		    }
		} elsif (/^DIG-RETRY-LIMIT\s*=/i) {
		    s/^DIG-RETRY-LIMIT\s*=\s*//i;
		    if (/^\d+$/) {
			$DiG_retries = $_;
		    } else {
			print STDERR "Non-numeric value for DIG-RETRY-LIMIT at line $line_num\nin configuration file `$file'\n";
			print STDERR "Resetting default value to 2.\n";
			$DiG_retries = 2;
		    }
		} elsif (/^DIG-TIMEOUT-LIMIT\s*=/i) {
		    s/^DIG-TIMEOUT-LIMIT\s*=\s*//i;
		    if (/^\d+$/) {
			$DiG_timeout = $_;
		    } else {
			print STDERR "Non-numeric value for DIG-TIMEOUT-LIMIT at line $line_num\nin configuration file `$file'\n";
			print STDERR "Resetting default value to 4.\n";
			$DiG_timeout = 4;
		    }
		} elsif (/^[^-+]/) {
		    print STDERR "Unrecognized data ($_) at line $line_num\nin configuration file `$file' - ignored.\n";
		} else {
		    #
		    # Assume that this line contains one or more `h2n'
		    # options that the user wishes to set as defaults
		    # without typing them on the command line or specifying
		    # them in a separate `-f' options file.
		    # Store the options in a temporary file for later
		    # submission to the PARSE_ARGS() subroutine.
		    #
		    unless ($options_file) {
			($data = $file) =~ s/.*\/\.?//;
			$options_file = "$debug_DIR/${data}_options";
			unless (&OPEN(*OPT, "> $options_file")) {
			    print STDERR "While processing command-line options found in the $Program configuration file\n($file), trying to open the temporary working file\n`$options_file' failed with the following error:\n  $!\n";
			    print STDERR "I give up ... sorry.\n";
			    close(*CONF);
			    exit(2);
			}
		    }
		    print OPT "$_\n";
		}
	    }
	    #
	    # Save the unique identity (device and inode)
	    # of the just-processed configuration file.
	    #
	    $rcfile = join(":", (stat(*CONF))[0..1]);
	    close(*CONF);
	    $data = $file;	# save $file before exiting `foreach' loop
	    last;		# don't read any more configuration files
	}
	chdir $CWD if $file eq ".h2nrc";
    }
    chdir $CWD;		# make sure we're back to our original directory
    unless (@local_networks) {
	#
	# Reassign the default values in case they got erased
	# by empty text in an `h2n' configuration file.
	#
	@local_networks = ("0/0");
	@local_subnetmasks = ("0.0.0.0");
    }
    if ($options_file) {
	close(*OPT);
	$error = &PARSE_ARGS(("-f", $options_file));
	if ($error) {
	    if ($error == 1) {
		print STDERR "NOTE: The above message was generated from an erroneous command-line option\n";
	    } else {
		print STDERR "NOTE: The above messages were generated from erroneous command-line options\n";
	    }
	    print STDERR "      found in the $Program configuration file `$data'.\nPlease make the necessary correction(s).\n";
	    exit(2);
	} elsif (!$debug) {
	    unlink($options_file);
	}
    }
    return;
}


sub GET_LOCAL_NETINFO {
    my ($i, $netbits, $netmask, $network, $our_host);

    # In order to verify a domain, a zone transfer must be obtained from
    # one of the domain's listed name servers.  This program will use DiG
    # to get every IP address of every listed name server so that they can
    # all be tried in a zone transfer request before having to give up.
    # Some IP addresses, however, may belong to inaccessible interfaces
    # of multi-homed bastion hosts.  Requesting a zone transfer from such
    # IP addresses will cause cause this program to hang until the connection
    # request times out.  We'll try to avoid these delays by sorting the
    # IP addresses using the information in the site-specific global arrays
    #
    #   @local_networks
    #   @local_subnetmasks
    #
    # This subroutine will initialize the following global arrays:
    #
    #   @our_nets
    #   @our_netbits
    #   @our_subnetmasks
    #   @our_subnets
    #
    # This information will allow any IP address to be sorted into one
    # of the following four categories:
    #
    #   1) the localhost itself
    #   2) subnets to which the localhost is directly connected
    #   3) networks to which the localhost has known connectivity
    #   4) all other networks
    #
    $our_host = hostname();
    ($tmp, $tmp, $tmp, $tmp, @our_addrs) = gethostbyname($our_host);

    # The "@our_addrs" array returned by the gethostbyname() function
    # contains the IP address(es) of the local host.  Each IP address
    # is a binary structure consisting of four unsigned character values.
    # We'll use the pack() and unpack() function with a template of `C4'
    # to do the necessary data manipulations.
    #
    # First, add the loopback address as a local host address.
    #
    push(@our_addrs, pack('C4', "127", "0", "0", "1"));
    @our_nets = @our_netbits = @our_subnetmasks = @our_subnets = ();
    for ($i = 0; $i < @local_networks; $i++) {
	#
	# Create the corresponding network-related packed data structures.
	#
	($network, $netbits) = split('/', $local_networks[$i]);
	$network .= ".0.0.0";			# ensure a 4-octet format
	$our_nets[$i] = pack('C4', split('\.', $network));
	$netmask = ((2 ** $netbits) - 1) << (32 - $netbits);

	# Convert the above 32-bit "$netmask" variable to a packed `C4'
	# data structure so that it will be compatible for bitwise AND
	# operations with the other IP-based data structures.
	#
	$our_netbits[$i] = pack('C4', ($netmask & 0xff000000) >> 24,
				      ($netmask & 0x00ff0000) >> 16,
				      ($netmask & 0x0000ff00) >> 8,
				       $netmask & 0x000000ff);

	# Each network specified in "@local_networks" will have a
	# corresponding subnet mask in "@local_subnetmasks".
	#
	$netmask = $local_subnetmasks[$i];
	$our_subnetmasks[$i] = pack('C4', split('\.', $netmask));
	$our_subnets[$i] = "";		# create defined values for all indices
    }
    foreach $addr (@our_addrs) {
	#
	# Find all subnets to which the local host is directly connected.
	#
	for ($i = 0; $i < @our_netbits; $i++) {
	    $network = $addr & $our_netbits[$i];
	    if ($network eq $our_nets[$i]) {
		$our_subnets[$i] = $addr & $our_subnetmasks[$i];
		last;
	    }
	}
    }
    return;
}


sub GEN_BOOT {
    my ($bcname, $bootdb, $bootdom, $bootzone, $line, $one, $sname);

    unless (-e "boot.cacheonly") {
	$bcname = "${Bwd}boot.cacheonly";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B option argument.\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	print F "\ndirectory  $Pwd\n\n";
	print F "cache      .\t\t\t\tdb.cache\n";
	print F "primary    0.0.127.in-addr.arpa\t\tdb.127.0.0\n";
	close(*F);
    }

    unless (-e "conf.cacheonly") {
	$bcname = "${Bwd}conf.cacheonly";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B option argument.\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	print F "\noptions {\n";
	print F "\tdirectory \"$Pwd\";\n};\n\n";
	if ($NeedHints) {
	    if ($new_fmt_Conffile) {
		print F "zone \".\" {\n\ttype hint;\n\tfile \"db.cache\";\n};\n";
	    } else {
		print F "zone \".\"\t\t\t{ type hint;\tfile \"db.cache\"; };\n";
	    }
	}
	if ($new_fmt_Conffile) {
	    print F "zone \"0.0.127.in-addr.arpa\" {\n\ttype master;\n\tfile \"db.127.0.0\";\n};\n";
	} else {
	    print F "zone \"0.0.127.in-addr.arpa\"\t{ type master;\tfile \"db.127.0.0\"; };\n";
	}
	close(*F);
    }
    
    if ($Bwd && $Bootfile !~ /^\//) {
	$bcname = "$Bwd$Bootfile";
    } else {
	$bcname = "$Bootfile";
    }
    unless (open(*F, "> $bcname")) {
	print STDERR "Unable to write `$bcname': $!\n";
	print STDERR "Check your -B and/or -b option argument(s).\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    }
    print F "\n";
    foreach $line (@BootOptions) {
	printf F "%s\n", $line;
    }
    print F "\ndirectory  $Pwd\n\n";
    print F "cache      .\t\t\t\tdb.cache\n";
    if ($MakeLoopbackZone) {
	print F "primary    0.0.127.in-addr.arpa\t\tdb.127.0.0\n";
    }
    foreach $line (@bootmsgs) {
	($bootdom, $bootdb) = split(' ', $line);
	printf F "primary    %s%s\n", &TAB($bootdom, 29), $bootdb;
    }
    $sname = "spcl-boot";
    if (-r $sname) {
	unless (open(*ADD, $sname)) {
	    print STDERR "Unable to read `$sname': $!\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	printf F "\n; ----- Begin contents of file `$sname' -----\n\n";
	while (<ADD>) {
	    print F;
	}
	close(*ADD);
	printf F "\n; ----- End of appended file `$sname' -----\n\n";
	print STDOUT "File `$sname' found and appended to `$bcname'.\n" if $verbose;
    }
    close(*F);

    if ($Bwd && $Conffile !~ /^\//) {
	$bcname = "$Bwd$Conffile";
    } else {
	$bcname = "$Conffile";
    }
    unless (open(*F, "> $bcname")) {
	print STDERR "Unable to write `$bcname': $!\n";
	print STDERR "Check your -B and/or +c option argument(s).\n";
	print STDERR "I give up ... sorry.\n";
	exit(2);
    }
    if ($Conf_prefile) {
	print F "\ninclude \"$Conf_prefile\";\n";
    }
    if ($CustomLogging) {
	if (@ConfLogging) {
	    print F "\nlogging {\n";
	    foreach $line (@ConfLogging) {
		printf F "\t%s\n", $line;
	    }
	    print F "};\n\n";
	} else {
	    print F "\nlogging {\n";
	    print F "\tcategory lame-servers { null; };\n";
	    print F "\tcategory cname { null; };\n";
	    print F "\tcategory security { default_syslog; };\n";
	    print F "};\n\n";
	}
    }
    if ($CustomOptions) {
	if (@ConfOptions) {
	    print F "\n" unless $CustomLogging;
	    print F "options {\n";
	    print F "\tdirectory \"$Pwd\";\n";
	    foreach $line (@ConfOptions) {
		printf F "\t%s\n", $line;
	    }
	    print F "};\n\n";
	}
    } else {
	print F "\n" unless $CustomLogging;
	print F "options {\n";
	print F "\tdirectory \"$Pwd\";\n";
	print F "};\n\n";
    }
    if ($NeedHints) {
	if ($new_fmt_Conffile) {
	    print F "zone \".\" {\n\ttype hint;\n\tfile \"db.cache\";\n};\n";
	} else {
	    print F "zone \".\"\t\t\t{ type hint;\tfile \"db.cache\"; };\n";
	}
    } elsif ($Conf_prefile) {
	print F "\n" unless $CustomLogging;
    }
    if ($MakeLoopbackZone) {
	if ($new_fmt_Conffile) {
	    print F "zone \"0.0.127.in-addr.arpa\" {\n\ttype master;\n\tfile \"db.127.0.0\";\n};\n";
	} else {
	    print F "zone \"0.0.127.in-addr.arpa\"\t{ type master;\tfile \"db.127.0.0\"; };\n";
	}
    }
    foreach $line (@bootmsgs) {
	($bootdom, $bootdb) = split(' ', $line);
	if ($new_fmt_Conffile) {
	    printf F "zone \"%s\" {\n\ttype master;\n\tfile \"%s\";", $bootdom, $bootdb;
	    foreach $one (@GlobalMasterZoneOptions) {
		printf F "\n\t%s", $one;
	    }
	    if (exists($MasterZoneOptions{"$bootdb"})) {
		foreach $one (split('\n', $MasterZoneOptions{"$bootdb"})) {
		    printf F "\n\t%s", $one;
		}
	    }
	    printf F "\n};\n";
	} else {
	    printf F "zone %s{ type master;\tfile \"%s\";", &TAB("\"$bootdom\"", 27), $bootdb;
	    foreach $one (@GlobalMasterZoneOptions) {
		printf F "\n                                  %s", $one;
	    }
	    if (exists($MasterZoneOptions{"$bootdb"})) {
		foreach $one (split('\n', $MasterZoneOptions{"$bootdb"})) {
		    printf F "\n                                  %s", $one;
		}
	    }
	    printf F " };\n";
	}
    }
    $sname = "spcl-conf";
    if (-r $sname) {
	print F "\ninclude \"$Pwd/$sname\";\n\n";
    }
    close(*F);

    if (defined($Bootsecaddr)) {
	$bcname = "${Bwd}boot.sec";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B and/or -Z option argument(s).\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	print  F "\n";
	foreach $line (@BootOptions) {
	    printf F "%s\n", $line;
	}
	print  F "\ndirectory  $Pwd\n\n";
	print  F "cache      .\t\t\t\tdb.cache\n";
	if ($MakeLoopbackZone) {
	    print  F "primary    0.0.127.in-addr.arpa\t\tdb.127.0.0\n";
	}
	foreach $line (@bootmsgs) {
	    ($bootdom, $bootdb) = split(' ', $line);
	    printf F "secondary  %s%s\n", &TAB($bootdom, 29), $Bootsecaddr;
	}
	$sname = "spcl-boot.sec";
	if (-r $sname) {
	    unless (open(*ADD, $sname)) {
		print STDERR "Unable to read `$sname': $!\n";
		print STDERR "I give up ... sorry.\n";
		exit(2);
	    }
	    printf F "\n; ----- Begin contents of file `$sname' -----\n\n";
	    while (<ADD>) {
		print F;
	    }
	    close(*ADD);
	    printf F "\n; ----- End of appended file `$sname' -----\n\n";
	    print STDOUT "File `$sname' found and appended to `$bcname'.\n" if $verbose;
	}
	close(*F);
    }

    if (defined($Confsecaddr)) {
	$bcname = "${Bwd}conf.sec";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B and/or -Z option argument(s).\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	if ($Conf_prefile) {
	    print F "\ninclude \"$Conf_prefile\";\n";
	}
	if ($CustomLogging) {
	    if (@ConfLogging) {
		print  F "\nlogging {\n";
		foreach $line (@ConfLogging) {
		    printf F "\t%s\n", $line;
		}
		print  F "};\n\n";
	    } else {
		print  F "\nlogging {\n";
		print  F "\tcategory lame-servers { null; };\n";
		print  F "\tcategory cname { null; };\n";
		print  F "\tcategory security { default_syslog; };\n";
		print  F "};\n\n";
	    }
	}
	if ($CustomOptions) {
	    if (@ConfOptions) {
		print F "\n" unless $CustomLogging;
		print F "options {\n";
		print F "\tdirectory \"$Pwd\";\n";
		foreach $line (@ConfOptions) {
		    printf F "\t%s\n", $line;
		}
		print F "};\n\n";
	    }
	} else {
	    print F "\n" unless $CustomLogging;
	    print F "options {\n";
	    print F "\tdirectory \"$Pwd\";\n";
	    print F "};\n\n";
	}
	if ($NeedHints) {
	    if ($new_fmt_Conffile) {
		print F "zone \".\" {\n\ttype hint;\n\tfile \"db.cache\";\n};\n";
	    } else {
		print F "zone \".\"\t\t\t{ type hint;\tfile \"db.cache\"; };\n";
	    }
	} elsif ($Conf_prefile) {
	    print F "\n" unless $CustomLogging;
	}
	if ($MakeLoopbackZone) {
	    if ($new_fmt_Conffile) {
		print F "zone \"0.0.127.in-addr.arpa\" {\n\ttype master;\n\tfile \"db.127.0.0\";\n};\n";
	    } else {
		print F "zone \"0.0.127.in-addr.arpa\"\t{ type master;\tfile \"db.127.0.0\"; };\n";
	    }
	}
	foreach $line (@bootmsgs) {
	    ($bootdom, $bootzone) = split(' ', $line);
	    if ($new_fmt_Conffile) {
		printf F "zone \"%s\" {\n\ttype slave;\n\tmasters { %s };", $bootdom, $Confsecaddr;
		foreach $one (@GlobalSlaveZoneOptions) {
		    printf F "\n\t%s", $one;
		}
		if (exists($SlaveZoneOptions{"$bootzone"})) {
		    foreach $one (split('\n', $SlaveZoneOptions{"$bootzone"})) {
			printf F "\n\t%s", $one;
		    }
		}
		printf F "\n};\n";
	    } else {
		printf F "zone %s{ type slave;\tmasters { %s };", &TAB("\"$bootdom\"", 27), $Confsecaddr;
		foreach $one (@GlobalSlaveZoneOptions) {
		    printf F "\n                                  %s", $one;
		}
		if (exists($SlaveZoneOptions{"$bootzone"})) {
		    foreach $one (split('\n', $SlaveZoneOptions{"$bootzone"})) {
			printf F "\n                                  %s", $one;
		    }
		}
		printf F " };\n";
	    }
	}
	$sname = "spcl-conf.sec";
	if (-r $sname) {
	    print F "\ninclude \"$Pwd/$sname\";\n\n";
	}
	close(*F);
    }

    if (defined($Bootsecsaveaddr)) {
	$bcname = "${Bwd}boot.sec.save";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B and/or -z option argument(s).\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	print  F "\n";
	foreach $line (@BootOptions) {
	    printf F "%s\n", $line;
	}
	print  F "\ndirectory  $Pwd\n\n";
	print  F "cache      .\t\t\t\tdb.cache\n";
	if ($MakeLoopbackZone) {
	    print  F "primary    0.0.127.in-addr.arpa\t\tdb.127.0.0\n";
	}
	foreach $line (@bootmsgs) {
	    ($bootdom, $bootdb) = split(' ', $line);
	    printf F "secondary  %s%s%s\n", &TAB($bootdom, 29), &TAB($Bootsecsaveaddr, 16), $bootdb;
	}
	$sname = "spcl-boot.sec.save";
	if (-r $sname) {
	    unless (open(*ADD, $sname)) {
		print STDERR "Unable to read `$sname': $!\n";
		print STDERR "I give up ... sorry.\n";
		exit(2);
	    }
	    printf F "\n; ----- Begin contents of file `$sname' -----\n\n";
	    while (<ADD>) {
		print F;
	    }
	    close(*ADD);
	    printf F "\n; ----- End of appended file `$sname' -----\n\n";
	    print STDOUT "File `$sname' found and appended to `$bcname'.\n" if $verbose;
	}
	close(*F);
    }

    if (defined($Confsecsaveaddr)) {
	$bcname = "${Bwd}conf.sec.save";
	unless (open(*F, "> $bcname")) {
	    print STDERR "Unable to write `$bcname': $!\n";
	    print STDERR "Check your -B and/or -z option argument(s).\n";
	    print STDERR "I give up ... sorry.\n";
	    exit(2);
	}
	if ($Conf_prefile) {
	    print F "\ninclude \"$Conf_prefile\";\n";
	}
	if ($CustomLogging) {
	    if (@ConfLogging) {
		print  F "\nlogging {\n";
		foreach $line (@ConfLogging) {
		    printf F "\t%s\n", $line;
		}
		print  F "};\n\n";
	    } else {
		print  F "\nlogging {\n";
		print  F "\tcategory lame-servers { null; };\n";
		print  F "\tcategory cname { null; };\n";
		print  F "\tcategory security { default_syslog; };\n";
		print  F "};\n\n";
	    }
	}
	if ($CustomOptions) {
	    if (@ConfOptions) {
		print F "\n" unless $CustomLogging;
		print F "options {\n";
		print F "\tdirectory \"$Pwd\";\n";
		foreach $line (@ConfOptions) {
		    printf F "\t%s\n", $line;
		}
		print F "};\n\n";
	    }
	} else {
	    print F "\n" unless $CustomLogging;
	    print F "options {\n";
	    print F "\tdirectory \"$Pwd\";\n";
	    print F "};\n\n";
	}
	if ($NeedHints) {
	    if ($new_fmt_Conffile) {
		print F "zone \".\" {\n\ttype hint;\n\tfile \"db.cache\";\n};\n";
	    } else {
		print F "zone \".\"\t\t\t{ type hint;\tfile \"db.cache\"; };\n";
	    }
	} elsif ($Conf_prefile) {
	    print F "\n" unless $CustomLogging;
	}
	if ($MakeLoopbackZone) {
	    if ($new_fmt_Conffile) {
		print F "zone \"0.0.127.in-addr.arpa\" {\n\ttype master;\n\tfile \"db.127.0.0\";\n};\n";
	    } else {
		print F "zone \"0.0.127.in-addr.arpa\"\t{ type master;\tfile \"db.127.0.0\"; };\n";
	    }
	}
	foreach $line (@bootmsgs) {
	    ($bootdom, $bootdb) = split(' ', $line);
	    $bootzone = $bootdb;
	    $bootdb =~ s/^db/bak/i;
	    if ($new_fmt_Conffile) {
		printf F "zone \"%s\" {\n\ttype slave;\n\tfile \"%s\";\n\tmasters { %s };", $bootdom, $bootdb, $Confsecsaveaddr;
		foreach $one (@GlobalSlaveZoneOptions) {
		    printf F "\n\t%s", $one;
		}
		if (exists($SlaveZoneOptions{"$bootzone"})) {
		    foreach $one (split('\n', $SlaveZoneOptions{"$bootzone"})) {
			printf F "\n\t%s", $one;
		    }
		}
		printf F "\n};\n";
	    } else {
		printf F "zone %s{ type slave;\tfile %smasters { %s };", &TAB("\"$bootdom\"", 27), &TAB("\"$bootdb\";", 19), $Confsecsaveaddr;
		foreach $one (@GlobalSlaveZoneOptions) {
		    printf F "\n                                  %s", $one;
		}
		if (exists($SlaveZoneOptions{"$bootzone"})) {
		    foreach $one (split('\n', $SlaveZoneOptions{"$bootzone"})) {
			printf F "\n                                  %s", $one;
		    }
		}
		printf F " };\n";
	    }
	}
	$sname = "spcl-conf.sec.save";
	if (-r $sname) {
	    print F "\ninclude \"$Pwd/$sname\";\n\n";
	}
	close(*F);
    }
}

