#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#This is ported forward from xCAT 1.3
#TODO: A lot of stuff was handled by the script portion of makedns, notably:
# db.cache
# forwarders
# chroot
# dnsallowq
# mucking with sysconfig
package xCAT_plugin::bind;
use strict;
no strict "refs";    #This code is as yet still very broken...

#use warnings;
use Sys::Hostname;
use Cwd;
use Getopt::Long;
use xCAT::Table;
use xCAT::MsgUtils;
use Data::Dumper;

sub handled_commands
{
    return {"makedns" => "bind"};
}

#NAME
#
#    h2n - Translate host table to name server file format
#    $Date: 1999/08/08 17:17:56 $  $Revision: 8.2 $
#
#SYNOPSIS
#
#    h2n -d DOMAIN -n NET [options]

# Various defaults
my $Host;
my $doaliases       = 1;
my $domx            = 1;
my $dowks           = 0;
my $dotxt           = 0;
my $dontdodomains   = 0;
my $Bootfile        = "/etc/named.conf";
my $DBDir           = "/var/named/";
my $Domain          = "";
my $Hostfile        = "/etc/hosts";
my $Commentfile     = "";
my $Commentfileread = 0;
my $User            = "root";
my $RespHost        = "";
my $RespUser        = "";
my $DefSerial       = 1;
my $DefRefresh      = 10800;
my $DefRetry        = 3600;
my $DefExpire       = 604800;
my $DefTtl          = 86400;
my $UseDefSOAValues = 0;
my $DefMxWeight     = 10;
my $Defsubnetmask   = "";
my $ForceSerial     = -1;
my $UseDateInSerial = 1;
my $DateSerial      = 0;
my $Version         = 8;
my $request;
my $callback;
my @forwarders;

#Declarations to alleviate use strict, since the code doesn't seem to be structured well enough to avoid it for these cases
my $Bootsecsaveaddr;
my $Bootsecaddr;
my @Networks;
my @bootmsgs_v4;
my @bootmsgs_v8;
my @elimpats;
my @cpats;
my @makesoa;
my $Domainfile;
my %cpatrel;
my @Servers;
my $Serial;
my $Refresh;
my @Mx;
my $Expire;
my %Hosts;
my %Comments;
my $Domainpattern;
my @Netpatterns;
my $Ttl;
my $Retry;
my %Cnames;
my %CommentRRs;
my $soa_warned;
my %Aliases;
my %Netfiles;

sub process_request
{
    $request    = shift;
    $callback   = shift;
    %Netfiles   = ();
    %Aliases    = ();
    $soa_warned = 0;
    my $canonical;
    my $aliases;
    %Comments    = ();
    %CommentRRs  = ();
    %Cnames      = ();
    %Hosts       = ();
    @Netpatterns = ();
    $DBDir       = "/var/named/";

    unless (-d $DBDir)
    {
        $DBDir = "/var/lib/named/";
    }

    # if both do not exist, make /var/named
    unless (-d $DBDir)
    {
        $DBDir = "/var/named/";
        my $cmd = "/bin/mkdir $DBDir";
        my $outref = xCAT::Utils->runcmd("$cmd", 0);
        if ($::RUNCMD_RC != 0)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Could not create $DBDir.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            return 1;
        }
    }

    $Host = hostname;
    $Host =~ s/\..*//;
    my $sitetab = xCAT::Table->new('site');
    unless ($sitetab)
    {
        my $rsp = {};
        $rsp->{data}->[0] = "No site table found.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return;
    }
    my @args = ();
    if ($request->{arg})
    {
        @args = @{$request->{arg}};
        @ARGV = @{$request->{arg}};
    }
    &checkusageandversion($callback);

    (my $fent) = $sitetab->getAttribs({key => 'forwarders'}, 'value');
    if ($fent and defined $fent->{value})
    {
        @forwarders = split /[,:;]/, $fent->{value};
    }
    unless (grep /^-d$/, @args)
    {
        (my $dent) = $sitetab->getAttribs({key => 'domain'}, 'value');
        if ($dent and $dent->{value})
        {
            push @args, "-d";
            $dent->{value} =~ s/\.$//;
            push @args, $dent->{value};
        }
    }
    unless (grep /^-s$/, @args)
    {
        push @args, "-s";
        push @args, $Host;
    }
    unless (grep /^-n$/, @args)
    {
        my $nettab = xCAT::Table->new('networks');
        unless ($nettab)
        {
            my $rsp = {};
            $rsp->{data}->[0] =
              "Unable to open networks table, has makenetworks been run?.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            return;
        }
        foreach (@{$nettab->getAllEntries()})
        {
            if ($_->{net} =~ /:/) {
                my $rsp = {};
                $rsp->{data}->[0] = "Ignoring IPv6 network, not supported in bind.pm (site.dnshandler=ddns if you want to do IPv6 DNS records";
                xCAT::MsgUtils->message("W",$rsp,$callback,1);
                next;
            }
            push @args, "-n";
            push @args, $_->{net} . ":" . $_->{mask};
        }
    }

    push(@bootmsgs_v4, "primary\t0.0.127.IN-ADDR.ARPA db.127.0.0\n");
    push(@bootmsgs_v8,
         qq|zone "0.0.127.IN-ADDR.ARPA" in {\n\ttype master;\n\tfile "db.127.0.0";\n\tnotify no;\n};\n\n|
         );

    &PARSEARGS($callback, @args);
    &FIXUP($callback);
    unless (open(HOSTS, $Hostfile))
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Unable to open $Hostfile\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        exit 1;
    }
    my $data;
    my $comment;
    my $addr;
    my $names;
  LINE: while (<HOSTS>)
    {
        next if /^[ \t]*#/;    # skip comment lines
        next if /^$/;          # skip empty lines
        chop;                  # remove the trailing newline
        tr/A-Z/a-z/;           # translate to lower case

        ($data, $comment) = split('#',     $_,    2);
        ($addr, $names)   = split(/[ 	]+/, $data, 2);
        if ($names =~ /^[ \t]*$/)
        {

            #$callback->({data=>["Bad line in hosts file ignored '$_'"]});
            next LINE;
        }
        $addr =~ s/^[    ]*//;
        $addr =~ s/[    ]*$//;
        if ($addr !~ /^\d+\.\d+\.\d+\.\d+$/)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Ignoring $addr (not a valid IPv4 address).\n";
            xCAT::MsgUtils->message("I", $rsp, $callback);
            next LINE;
        }

        # Match -e args
        my $netpat;
        foreach $netpat (@elimpats)
        {
            next LINE if (/[.\s]$netpat/);
        }

        # Process -c args
        foreach $netpat (@cpats)
        {
            if (/\.$netpat/)
            {
                ($canonical, $aliases) = split(' ', $names, 2);
                $canonical =~ s/\.$netpat//;
                #If we feed in names with, say, underscores, bind complains usually due to default check-names behavior.  Later, we may should support putting check-names to ignore, but for now let them know it won't work and skip it
                unless ($canonical =~ /^[a-z0-9-]+$/i) {
                    xCAT::MsgUtils->message("E", {errorcode=>[1],error=>["$canonical contains invalid characters, skipping entry"]}, $callback, 1);
                    next LINE;
                }
                if ($Cnames{$canonical} != 1)
                {
                    printf DOMAIN "%-20s IN  CNAME %s.%s.\n", $canonical,
                      $canonical, $cpatrel{$netpat};
                    $Cnames{$canonical} = 1;
                }
                next LINE;
            }
        }

        # Check that the address is in the address list.
        my $match = 'none';
        foreach $netpat (@Netpatterns)
        {
            $match = $netpat, last
              if ($addr =~ /^$netpat\./ or $addr =~ /^$netpat$/);
        }
        next if ($match eq 'none');

        ($canonical, $aliases) = split(' ', $names, 2);   # separate out aliases
        next if ($dontdodomains && $canonical =~ /\./);   # skip domain names
        $canonical =~ s/$Domainpattern//;    # strip off domain if there is one
        unless ($canonical =~ /^[a-z0-9-]+$/i) {
            xCAT::MsgUtils->message("E", {errorcode=>[1],error=>["$canonical contains invalid characters, skipping entry"]}, $callback, 1);
            next;
         }
        $Hosts{$canonical} .= $addr . " ";   # index addresses by canonical name
        $Aliases{$addr} .= $aliases . " ";   # index aliases by address
        $Comments{"$canonical-$addr"} = $comment;

        # Print PTR records
        my $file = $Netfiles{$match};
        printf $file "%-30s\tIN  PTR   %s.%s.\n", &REVERSE($addr), $canonical,
          $Domain;
    }

    #
    # Go through the list of canonical names.
    # If there is more than 1 address associated with the
    # name, it is a multi-homed host.  For each address
    # look up the aliases since the aliases are associated
    # with the address, not the canonical name.
    #
    foreach $canonical (keys %Hosts)
    {
        my @addrs = split(' ', $Hosts{$canonical});
        my $numaddrs = $#addrs + 1;
        foreach my $addr (@addrs)
        {

            #
            # Print address record for canonical name.
            #
            if ($Cnames{$canonical} != 1)
            {
                printf DOMAIN "%-20s IN  A     %s\n", $canonical, $addr;
            }
            else
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "$canonical - cannot create A record because CNAME exists for name.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            }

            #
            # Print cname or address records for each alias.
            # If this is a multi-homed host, print an address
            # record for each alias.  If this is a single address
            # host, print a cname record.
            #
            my $alias;
            if ($doaliases)
            {
                my @aliases = split(' ', $Aliases{$addr});
                foreach $alias (@aliases)
                {

                    #
                    # Skip over the alias if the alias and canonical
                    # name only differ in that one of them has the
                    # domain appended to it.
                    #
                    next
                      if ($dontdodomains && $alias =~ /\./); # skip domain names
                    $alias =~ s/$Domainpattern//;
                    if ($alias eq $canonical)
                    {
                        next;
                    }
                    unless ($alias  =~ /^[a-z0-9-]+$/i) {
                        xCAT::MsgUtils->message("E", {errorcode=>[1],error=>["$canonical alias $alias contains invalid characters, skipping entry"]}, $callback, 1);
                        next;
                    }

                    my $aliasforallnames = 0;
                    if ($numaddrs > 1)
                    {

                        #
                        # If alias exists for *all* addresses of this host, we
                        # can use a CNAME instead of an address record.
                        #
                        my $aliasforallnames = 1;
                        my $xalias = $alias . " "; # every alias ends with blank
                        my @xaddrs = split(' ', $Hosts{$canonical});
                        foreach my $xaddr (@xaddrs)
                        {
                            if (!($Aliases{$xaddr} =~ /\b$xalias/))
                            {
                                $aliasforallnames = 0;
                            }
                        }
                    }

                    if (($numaddrs > 1) && !$aliasforallnames)
                    {
                        printf DOMAIN "%-20s IN  A     %s\n", $alias, $addr;
                    }
                    else
                    {

                        #
                        # Flag aliases that have already been used
                        # in CNAME records or have A records.
                        #
                        if (($Cnames{$alias} != 1) && (!$Hosts{$alias}))
                        {
                            printf DOMAIN "%-20s IN  CNAME %s.%s.\n", $alias,
                              $canonical, $Domain;
                            $Cnames{$alias} = 1;
                        }
                        else
                        {
                            my $rsp = {};
                            $rsp->{data}->[0] =
                              "$alias - CNAME or A exists already. alias ignored.\n";
                            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                        }
                    }

                    if ($aliasforallnames)
                    {

                        #
                        # Since a CNAME record was created, remove this
                        # name from the alias list so we don't encounter
                        # it again for the next address of this host.
                        #
                        my $xalias = $alias . " "; # every alias ends with blank
                        my @xaddrs = split(' ', $Hosts{$canonical});
                        my $xaddr;
                        foreach $xaddr (@xaddrs)
                        {
                            $Aliases{$xaddr} =~ s/\b$xalias//;
                        }
                    }
                }
            }
        }
        if ($domx)
        {
            &MX($callback, $canonical, @addrs);
        }
        if ($dotxt)
        {
            &TXT($canonical, @addrs);
        }
        if ($Commentfile ne "")
        {
            &DO_COMMENTS($callback, $canonical, @addrs);
        }
    }

    # Deal with spcl's
    if (-r "spcl.$Domainfile")
    {
        print DOMAIN "\$INCLUDE spcl.$Domainfile\n";
    }
    my $file;
    my $n;
    foreach $n (@Networks)
    {
        if (-r "spcl.$n")
        {
            $file = "DB.$n";
            print $file "\$INCLUDE spcl.$n\n";
        }
    }

    # generate boot.* files
    &GEN_BOOT($callback);
    my $rsp = {};
    $rsp->{data}->[0] = "Setup of DNS complete.";
    xCAT::MsgUtils->message("I", $rsp, $callback);
    return 0;
}

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

    if (!$Commentfileread)
    {
        unless (open(F, $Commentfile))
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Unable to open file $Commentfile: $!.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }
        $Commentfileread++;
        while (<F>)
        {
            chop;
            my ($key, $c) = split(':', $_, 2);
            $CommentRRs{$key} = $c;
        }
        close(F);
    }

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

    @c = split(' ', $comments);
    foreach $c (@c)
    {
        if ($CommentRRs{$c})
        {
            printf DOMAIN "%-20s %s\n", $canonical, $CommentRRs{$c};
        }
    }
}

#
# Generate MX record data
#
sub MX
{
    my ($callback, $canonical, @addrs) = @_;
    my ($first, $a, $key, $comments);

    if ($Cnames{$canonical})
    {
        my $rsp = {};
        $rsp->{data}->[0] =
          "$canonical - cannot create MX record because CNAME exists for name.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return;
    }
    $first = 1;

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

    if ($comments !~ /\[no smtp\]/)
    {

        # Add WKS if requested
        if ($dowks)
        {
            foreach $a (@addrs)
            {
                printf DOMAIN "%-20s IN  WKS   %s TCP SMTP\n", $canonical, $a;
            }
        }
        printf DOMAIN "%-20s IN  MX    %s %s.%s.\n", $canonical, $DefMxWeight,
          $canonical, $Domain;
        $first = 0;
    }
    if ($#Mx >= 0)
    {
        foreach $a (@Mx)
        {
            if ($first)
            {
                printf DOMAIN "%-20s IN  MX    %s\n", $canonical, $a;
                $first = 0;
            }
            else
            {
                printf DOMAIN "%-20s IN  MX    %s\n", "", $a;
            }
        }
    }

}

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

    foreach $a (@addrs)
    {
        $key = "$canonical-$a";
        $comments .= " $Comments{$key}";
    }
    $comments =~ s/\[no smtp\]//g;
    $comments =~ s/^\s*//;
    $comments =~ s/\s*$//;

    if ($comments ne "")
    {
        printf DOMAIN "%s IN  TXT   \"%s\"\n", $canonical, $comments;
    }
}

#
# Create the SOA record at the beginning of the file
#
sub MAKE_SOA
{
    my ($callback, $fname, $file) = @_;
    my ($s);

    if (-s $fname)
    {

        unless (open($file, "$fname"))
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Unable to open $fname: $!.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }
        $_ = <$file>;
        chop;
        if (/\($/)
        {
            my $junk;
            if (!$soa_warned)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Converting SOA format to new style.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                $soa_warned++;
            }
            if ($ForceSerial > 0)
            {
                $Serial = $ForceSerial;
            }
            else
            {
                ($Serial, $junk) = split(' ', <$file>, 2);
                $Serial++;
                if ($UseDateInSerial && ($DateSerial > $Serial))
                {
                    $Serial = $DateSerial;
                }
            }
            ($Refresh, $junk) = split(' ', <$file>, 2);
            ($Retry,   $junk) = split(' ', <$file>, 2);
            ($Expire,  $junk) = split(' ', <$file>, 2);
            ($Ttl,     $junk) = split(' ', <$file>, 2);
        }
        else
        {
            if (/TTL/)
            {
                $_ = <$file>;
            }
            split(' ');
            if ($#_ == 11)
            {
                if ($ForceSerial > 0)
                {
                    $Serial = $ForceSerial;
                }
                else
                {
                    $Serial = ++$_[6];
                    if ($UseDateInSerial && ($DateSerial > $Serial))
                    {
                        $Serial = $DateSerial;
                    }
                }
                $Refresh = $_[7];
                $Retry   = $_[8];
                $Expire  = $_[9];
                $Ttl     = $_[10];
            }
            else
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Improper format SOA in $fname.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }
        }
        if ($UseDefSOAValues)
        {
            $Refresh = $DefRefresh;
            $Retry   = $DefRetry;
            $Expire  = $DefExpire;
            $Ttl     = $DefTtl;
        }
        close($file);
    }
    else
    {
        if ($ForceSerial > 0)
        {
            $Serial = $ForceSerial;
        }
        else
        {
            $Serial = $DefSerial;
            if ($UseDateInSerial && ($DateSerial > $Serial))
            {
                $Serial = $DateSerial;
            }
        }
        $Refresh = $DefRefresh;
        $Retry   = $DefRetry;
        $Expire  = $DefExpire;
        $Ttl     = $DefTtl;
        close($file);
    }

    unless (open($file, "> $fname"))
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Unable to open $fname: $!.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        exit 1;
    }
    print $file '$TTL 86400' . "\n";
    print $file "\@ IN  SOA $RespHost $RespUser ";
    print $file "( $Serial $Refresh $Retry $Expire $Ttl )\n";
    foreach $s (@Servers)
    {
        print $file "  IN  NS  $s\n";
    }
    print $file "\n";
}

#
# Reverse the octets of an IP address and append
# in-addr.arpa.
#
sub REVERSE
{
    join('.', reverse(split('\.', $_[0]))) . '.IN-ADDR.ARPA.';
}

#
# Establish what we will be using for SOA records
#
sub FIXUP
{
    my $callback = shift;
    my ($s);

    if ($Host =~ /\./)
    {
        $RespHost = "$Host.";
    }
    else
    {
        $RespHost = "$Host.$Domain.";
    }
    $RespHost =~ s/\.\././g;

    if ($User =~ /@/)
    {    # -u user@...
        if ($User =~ /\./)
        {
            $RespUser = "$User.";    # -u user@terminator.movie.edu
        }
        else
        {
            $RespUser = "$User.$Domain.";    # -u user@terminator
        }
        $RespUser =~ s/@/./;
    }
    elsif ($User =~ /\./)
    {
        $RespUser = "$User.";                # -u user.terminator.movie.edu
    }
    else
    {
        $RespUser = "$User.$RespHost";       # -u user
    }
    $RespUser =~ s/\.\././g;                 # Strip any ".."'s to "."

    # Clean up nameservers
    if (!@Servers)
    {
        my $rsp = {};
        $rsp->{data}->[0] =
          "No -s option specified.  Assuming \"-s $Host.$Domain\".\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        push(@Servers, "$Host.$Domain.");
    }
    else
    {
        foreach $s (@Servers)
        {
            if ($s !~ /\./)
            {
                $s .= ".$Domain";
            }
            if ($s !~ /\.$/)
            {
                $s .= ".";
            }
        }
    }

    # Clean up MX hosts
    foreach $s (@Mx)
    {
        $s =~ s/:/ /;
        if ($s !~ /\./)
        {
            $s .= ".$Domain";
        }
        if ($s !~ /\.$/)
        {
            $s .= ".";
        }
    }

    # Now open boot file (named.conf)  and print saved data
    unless (open(BOOT, "> $Bootfile"))
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Unable to  open $Bootfile.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        exit 1;
    }

    #
    # Write either the version 4 boot file directives or the
    # version 8 boot file directives.
    #

    if ($Version == 4)
    {
        print BOOT "\ndirectory $DBDir\n";
        foreach my $line (@bootmsgs_v4)
        {
            print BOOT $line;
        }
        print BOOT "cache\t. db.cache\n";
        if (-r "spcl.boot")
        {
            print BOOT "include\tspcl.boot\n";
        }
    }
    else
    {
        print BOOT qq|\noptions {\n\tdirectory "$DBDir";\n|;
        if (@forwarders)
        {
            print BOOT qq|\tforwarders {\n|;
            foreach (@forwarders)
            {
                print BOOT qq|\t\t$_;\n|;
            }
            print BOOT qq|\t};\n|;
        }
        if (-r "spcl.options")
        {
            print BOOT "\t# These options came from the file spcl.options\n";

            #
            # Copy the options in since "include" cannot be used
            # within a statement.
            #
            unless (open(OPTIONS, "<spcl.options"))
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Unable to open spcl.options.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit 1;
            }
            while (<OPTIONS>)
            {
                print BOOT;
            }
            close(OPTIONS);
        }
        print BOOT qq|};\n\n|;
        foreach my $line (@bootmsgs_v8)
        {
            print BOOT $line;
        }

        # hint zone is also needed for DNS forwarders on AIX
        if (xCAT::Utils->isAIX())
        {
            print BOOT
              qq|zone "." in {\n\ttype hint;\n\tfile "db.cache";\n};\n\n|;
        }
        else
        {
            unless (@forwarders)
            {
                print BOOT
                  qq|zone "." in {\n\ttype hint;\n\tfile "db.cache";\n};\n\n|;
            }
        }
        
        if (-r "spcl.boot")
        {
            print BOOT qq|include "spcl.boot";\n\n|;
        }
    }

    close(BOOT);

    # Go ahead and start creating files and making SOA's
    my $x1;
    my $x2;
    foreach my $i (@makesoa)
    {
        ($x1, $x2) = split(' ', $i);
        &MAKE_SOA($callback, $x1, $x2);
    }
    printf DOMAIN "%-20s IN  A     127.0.0.1\n", "localhost";

    my $file = "DB.127.0.0.1";
    &MAKE_SOA($callback, $DBDir . "db.127.0.0", $file);
    if (xCAT::Utils->isAIX())
    {
        # if forwarders is set, we need to create the hint file for root name servers.
        if (@forwarders)
        {
            my $tmpfile = $DBDir . "db.cache";
            my $cmd = qq~dig @"$forwarders[0]" . ns >> $tmpfile~;
            my $outref = xCAT::Utils->runcmd("$cmd", 0);
            if ($::RUNCMD_RC != 0)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Could not run command: $cmd.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit 1;
            }
        }
        else
        {
            my $nothing;
            open($nothing, ">>", $DBDir . "db.cache");
            close($nothing);

        }
    }
    else
    {
        my $nothing;
        open($nothing, ">>", $DBDir . "db.cache");
        close($nothing);

    }
    printf $file "%-30s\tIN  PTR   localhost.\n", &REVERSE("127.0.0.1");
    close($file);
}

sub PARSEARGS
{
    my ($callback, @args) = @_;
    my ($i, $net, $subnetmask, $option, $tmp1);
    my ($file, @newargs, @targs);
    my ($sec, $min, $hour, $mday, $mon, $year, $rest);
    ($sec, $min, $hour, $mday, $mon, $year, $rest) = localtime(time);
    $DateSerial =
      ($mday * 100) + (($mon + 1) * 10000) + (($year + 1900) * 1000000);

    $i = 0;
    while ($i <= $#args)
    {
        $option = $args[$i];
        if ($option eq "-d")
        {
            if ($Domain ne "")
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Only one -d option allowed.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }
            $Domain        = $args[++$i];
            $Domainpattern = "." . $Domain;
            $Domainpattern =~ s/\./\\./g;    # for stripping off domain

            # Add entry to the boot file.
            $Domainfile = $Domain;
            $Domainfile =~ s/\..*//;
            push(@makesoa,     $DBDir . "db.$Domainfile DOMAIN");
            push(@bootmsgs_v4, "primary\t$Domain db.$Domainfile\n");
            push(@bootmsgs_v8,
                 qq|zone "$Domain" in {\n\ttype master;\n\tfile "db.$Domainfile";\n};\n\n|
                 );

        }
        elsif ($option eq "-f")
        {
            $file = $args[++$i];
            unless (open(F, $file))
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Unable to open args file $file: $!.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit 1;
            }
            while (<F>)
            {
                next if (/^#/);
                next if (/^$/);
                chop;
                @targs = split(' ');
                push(@newargs, @targs);
            }
            close(F);
            &PARSEARGS($callback, @newargs);

        }
        elsif ($option eq "-z")
        {
            $Bootsecsaveaddr = $args[++$i];
            if (!defined($Bootsecaddr))
            {
                $Bootsecaddr = $Bootsecsaveaddr;
            }

        }
        elsif ($option eq "-Z")
        {
            $Bootsecaddr = $args[++$i];
            if (!defined($Bootsecsaveaddr))
            {
                $Bootsecsaveaddr = $Bootsecaddr;
            }

        }
        elsif ($option eq "-b")
        {
            $Bootfile = $args[++$i];

        }
        elsif ($option eq "-A")
        {
            $doaliases = 0;

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

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

        }
        elsif ($option eq "-D")
        {
            $dontdodomains = 1;

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

        }
        elsif ($option eq "-u")
        {
            $User = $args[++$i];

        }
        elsif ($option eq "-s")
        {
            while ($args[++$i] !~ /^-/ && $i <= $#args)
            {
                push(@Servers, $args[$i]);
            }
            $i--;

        }
        elsif ($option eq "-m")
        {
            if ($args[++$i] !~ /:/)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "Improper format for -m option ignored ($args[$i]).\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            }
            push(@Mx, $args[$i]);

        }
        elsif ($option eq "-c")
        {
            my $tmp1 = $args[++$i];
            if ($tmp1 !~ /\./)
            {
                $tmp1 .= ".$Domain";
            }
            if ($Domain eq $tmp1)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "Domain for -c option must not match domain for -d option.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }
            my $tmp2 = $tmp1;
            $tmp2 =~ s/\./\\./g;
            $cpatrel{$tmp2} = $tmp1;
            push(@cpats, $tmp2);

        }
        elsif ($option eq "-e")
        {
            $tmp1 = $args[++$i];
            if ($tmp1 !~ /\./)
            {
                $tmp1 .= ".$Domain";
            }
            $tmp1 =~ s/\./\\./g;
            push(@elimpats, $tmp1);

        }
        elsif ($option eq "-h")
        {
            $Host = $args[++$i];

        }
        elsif ($option eq "-o")
        {
            if ($args[++$i] !~ /^[:\d]*$/
                || split(':', $args[$i]) != 4)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Improper format for -o ($args[$i]).\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }
            ($DefRefresh, $DefRetry, $DefExpire, $DefTtl) =
              split(':', $args[$i]);
            $UseDefSOAValues = 1;

        }
        elsif ($option eq "-i")
        {
            $ForceSerial = $args[++$i];

        }
        elsif ($option eq "-H")
        {
            $Hostfile = $args[++$i];
            if (!-r $Hostfile || -z $Hostfile)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "Invalid file specified for -H ($Hostfile).\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }

        }
        elsif ($option eq "-C")
        {
            $Commentfile = $args[++$i];
            if (!-r $Commentfile || -z $Commentfile)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "Invalid file specified for -C ($Commentfile).\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }

        }
        elsif ($option eq "-N")
        {
            $Defsubnetmask = $args[++$i];
            if ($Defsubnetmask !~ /^[.\d]*$/
                || split('\.', $Defsubnetmask) != 4)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Improper subnet mask ($Defsubnetmask).\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                exit(1);
            }
            if ($#Networks >= 0)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "-N option should be specified before any -n options.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            }

        }
        elsif ($option eq "-n")
        {
            (my $tnet, $subnetmask) = split(':', $args[++$i]);
            $net = "";
            my @netm  = split(/\./, $subnetmask);
            my @tnets = split(/\./, $tnet);
            foreach (0 .. 3)
            {
                my $res = ($tnets[$_] + 0) & ($netm[$_] + 0);
                if ($netm[$_])
                {
                    $net .= $res . '.';
                }
            }
            $net =~ s/\.$//;

            if (not $net)
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "Empty network found in networks table (i.e. ,,),This is almost certainly going to cause a problem....\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            }
            if ($subnetmask eq "")
            {
                my $rsp = {};
                $rsp->{data}->[0] =
                  "$net has no defined netmask in the networks table, the result will probably be wrong.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                foreach $tmp1 (&SUBNETS($net, $Defsubnetmask))
                {
                    &BUILDNET($tmp1);
                }
            }
            else
            {
                if ($subnetmask !~ /^[.\d]*$/
                    || split('\.', $subnetmask) != 4)
                {
                    my $rsp = {};
                    $rsp->{data}->[0] = "Improper subnet mask ($subnetmask).\n";
                    xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                    exit(1);
                }
                foreach $tmp1 (&SUBNETS($net, $subnetmask))
                {
                    &BUILDNET($tmp1);
                }
            }

        }
        else
        {
            if ($option =~ /^-.*/)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Unknown option: $option ... ignored.\n";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            }
        }
        $i++;
    }

    if (!@Networks || $Domain eq "")
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Must specify one -d and at least one -n.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        exit(1);
    }
}

sub BUILDNET
{
    my ($net) = @_;

    push(@Networks, $net);

    #
    # Create pattern to match against.
    # The dots must be changed to \. so they
    # aren't used as wildcards.
    #
    my $netpat = $net;
    $netpat =~ s/\./\\./g;
    push(@Netpatterns, $netpat);

    #
    # Create db files for PTR records.
    # Save the file names in an array for future use.
    #
    my $netfile = "DB.$net";
    $Netfiles{$netpat} = $netfile;
    push(@makesoa, $DBDir . "db.$net $netfile");

    # Add entry to the boot file.
    my $revaddr = &REVERSE($net);
    chop($revaddr);    # remove trailing dot
    push(@bootmsgs_v4, "primary $revaddr db.$net\n");
    push(@bootmsgs_v8,
         qq|zone "$revaddr" in {\n\ttype master;\n\tfile "db.$net";\n};\n\n|);
}

#
# Calculate all the subnets from a network number and mask.
# This was originally written for awk, not perl.
#
sub SUBNETS
{
    my ($network, $mask) = @_;
    my (@ans, @net, @mask, $buf, $number, $i, $j, $howmany);

    @net  = split(/\./, $network);
    @mask = split(/\./, $mask);
    $number = '';

    #
    # Only expand bytes 1, 2, or 3
    # for DNS purposes
    #
    for ($i = 0 ; $i < 4 ; $i++)
    {
        if ($mask[$i] == 255)
        {
            $number = $number . $net[$i] . '.';
        }
        elsif (($mask[$i] == 0) || $mask[$i] eq '')
        {
            push(@ans, $network);
            last;
        }
        else
        {

            #
            # This should be done as a bit-wise or
            # but awk does not have an or symbol
            #
            $howmany = 255 - $mask[$i];
            for ($j = 0 ; $j <= $howmany ; $j++)
            {
                if ($net[$i] + $j <= 255)
                {
                    $buf = sprintf("%s%d", $number, $net[$i] + $j);
                    push(@ans, $buf);
                }
            }
            last;
        }
    }

    if ($#ans == -1)
    {
        push(@ans, $network);
    }

    @ans;
}

sub GEN_BOOT
{
    $callback = shift;
    my ($revaddr, $n);

    if (0)
    {    #! -e "boot.cacheonly") { DISABLE THIS PART
            #
            # Create a boot file for a cache-only server
            #
        unless (open(F, ">boot.cacheonly"))
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Unable to open boot.cacheonly: $!.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }
        if ($Version == 4)
        {
            print F "directory\t$DBDir\n";
            print F "primary\t\t0.0.127.IN-ADDR.ARPA    db.127.0.0\n";
            print F "cache\t\t.                       db.cache\n";
            if (-r "spcl.cacheonly")
            {
                printf F "include\t\tspcl.cacheonly\n";
            }
            close(F);
        }
        else
        {
            print F qq|\noptions {\n\tdirectory "$DBDir";\n|;
            if (@forwarders)
            {
                print F qq|\tforwarders {\n|;
                foreach (@forwarders)
                {
                    print F qq|\t\t$_;\n|;
                }
                print F qq|\t};\n|;
            }
            if (-r "spcl.options")
            {
                print F "\t# These options came from the file spcl.options\n";

                #
                # Copy the options in since "include" cannot be used
                # within a statement.
                #

                unless (open(OPTIONS, "<spcl.options"))
                {
                    my $rsp = {};
                    $rsp->{data}->[0] = "Unable to open boot.cacheonly: $!.\n";
                    xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                    exit 1;
                }

                while (<OPTIONS>)
                {
                    print F;
                }
                close(OPTIONS);
            }
            print F qq|};\n\n|;
            print F qq|zone "0.0.127.IN-ADDR.ARPA" in {\n\ttype master;|;
            print F qq|\n\tfile "db.127.0.0";|;
            print F qq|\n\tnotify no;\n};\n\n|;

            #print F qq|zone "." in {\n\ttype hint;\n\tfile "db.cache";\n};\n\n|;
            if (-r "spcl.cacheonly")
            {
                print F qq|include "spcl.cacheonly";\n\n|;
            }
        }
    }

    #
    # Create a 2 boot files for a secondary (slave) servers.
    # One boot file doesn't save the zone data in a file.  The
    # other boot file does save the zone data in a file.
    #
    if (defined($Bootsecaddr))
    {
        unless (open(F, ">boot.sec"))
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Unable to open boot.sec: $!.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }

        if ($Version == 4)
        {
            print F "directory\t$DBDir\n";
            print F "primary\t\t0.0.127.IN-ADDR.ARPA    db.127.0.0\n";
            printf F "secondary\t%-23s $Bootsecaddr\n", $Domain;
            foreach $n (@Networks)
            {
                $revaddr = &REVERSE($n);
                chop($revaddr);
                printf F "secondary\t%-23s $Bootsecaddr\n", $revaddr;
            }
            print F "cache\t\t.                       db.cache\n";
            if (-r "spcl.boot")
            {
                printf F "include\t\tspcl.boot\n";
            }
        }
        else
        {
            print F qq|\noptions {\n\tdirectory "$DBDir";\n|;
            if (-r "spcl.options")
            {
                print F "\t# These options came from the file spcl.options\n";

                #
                # Copy the options in since "include" cannot be used
                # within a statement.
                #
                unless (open(OPTIONS, "<spcl.options"))
                {
                    my $rsp = {};
                    $rsp->{data}->[0] = "Unable to open spcl.options.\n";
                    xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                    exit 1;
                }

                while (<OPTIONS>)
                {
                    print F;
                }
                close(OPTIONS);
            }
            print F qq|};\n\n|;
            print F qq|zone "0.0.127.IN-ADDR.ARPA" in {\n\ttype master;|;
            print F qq|\n\tfile "db.127.0.0";|;
            print F qq|\n\tnotify no;\n};\n\n|;
            print F qq|zone "$Domain" in {\n\ttype slave;\n\tmasters {|;
            print F qq| $Bootsecaddr; };\n};\n\n|;

            foreach $n (@Networks)
            {
                $revaddr = &REVERSE($n);
                chop($revaddr);
                print F qq|zone "$revaddr" in {\n\ttype slave;\n\tmasters {|;
                print F qq| $Bootsecaddr; };\n};\n\n|;
            }

            #print F qq|zone "." in {\n\ttype hint;\n\tfile "db.cache";\n};\n\n|;
            if (-r "spcl.boot")
            {
                print F qq|include "spcl.boot";\n\n|;
            }
        }
        close(F);

        unless (open(F, ">boot.sec.save"))
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Unable to open  boot.sec.save: $!.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }

        if ($Version == 4)
        {
            print F "directory\t$DBDir\n";
            print F "primary\t\t0.0.127.IN-ADDR.ARPA    db.127.0.0\n";
            printf F "secondary\t%-23s $Bootsecsaveaddr db.%s\n", $Domain,
              $Domainfile;
            foreach $n (@Networks)
            {
                $revaddr = &REVERSE($n);
                chop($revaddr);
                printf F "secondary\t%-23s $Bootsecsaveaddr db.%s\n", $revaddr,
                  $n;
            }
            print F "cache\t\t.                       db.cache\n";
            if (-r "spcl.boot")
            {
                printf F "include\t\tspcl.boot\n";
            }
        }
        else
        {
            print F qq|\noptions {\n\tdirectory "$DBDir";\n|;
            if (-r "spcl.options")
            {
                print F "\t# These options came from the file spcl.options\n";

                #
                # Copy the options in since "include" cannot be used
                # within a statement.
                #
                unless (open(OPTIONS, "<spcl.options"))
                {
                    my $rsp = {};
                    $rsp->{data}->[0] = "Can't open spcl.options.\n";
                    xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                    exit 1;
                }
                while (<OPTIONS>)
                {
                    print F;
                }
                close(OPTIONS);
            }
            print F qq|};\n\n|;
            print F qq|zone "0.0.127.IN-ADDR.ARPA" in {\n\ttype master;|;
            print F qq|\n\tfile "db.127.0.0";|;
            print F qq|\n\tnotify no;\n};\n\n|;

            print F
              qq|zone "$Domain" in {\n\ttype slave;\n\tfile "db.$Domainfile";|;
            print F qq|\n\tmasters { $Bootsecsaveaddr; };\n};\n\n|;

            foreach $n (@Networks)
            {
                $revaddr = &REVERSE($n);
                chop($revaddr);
                print F
                  qq|zone "$revaddr" in {\n\ttype slave;\n\tfile "db.$n";\n\tmasters {|;
                print F qq| $Bootsecsaveaddr; };\n};\n\n|;
            }

            #print F qq|zone "." in {\n\ttype hint;\n\tfile "db.cache";\n};\n\n|;
            if (-r "spcl.boot")
            {
                print F qq|include "spcl.boot";\n\n|;
            }
        }
        close(F);
    }
}

# subroutine to display the usage
sub checkusageandversion
{
    my $callback = shift;

    # parse the options
    if (
        GetOptions('h|help'    => \$::HELP,
                   'v|version' => \$::VERSION)
      )
    {
        if ($::HELP)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Usage:";
            $rsp->{data}->[1] = "  makedns <options>";
            $rsp->{data}->[2] = "  makedns [-h|--help|-v|--version]";
            $rsp->{data}->[3] = "     <options> See man makedns";
            xCAT::MsgUtils->message("I", $rsp, $callback);
            exit 0;
        }
        if ($::VERSION)
        {
            my $version = xCAT::Utils->Version();
            my $rsp     = {};
            $rsp->{data}->[0] = "$version";
            xCAT::MsgUtils->message("I", $rsp, $callback);
            exit 0;
        }
    }
    return;
}

1;