#!/usr/bin/eperl

# Displays/alters recursive dns settings for the local dns server
# refer http://uw713doc.sco.com/en/NET_bind/Bv9ARM.html for details

# $Id: recursivedns,v 1.1.2.1 2006/09/07 09:41:00 mansoor Exp $

use lib ($ENV{'OCW_SVCPATH'} || "/usr/lib/opcenter")."/bind";
use lib ($ENV{'OCW_SVCPATH'} || "/usr/lib/opcenter")."/cmdline_common";

use strict;

my $usage = 
"Usage: recursivedns [on/off] [+/-all] [+allowedhost1 [+allowedhost2] ...] [-deniedhost1 [-deniedhost2 ...]
This program manages recursive dns settings on the local DNS server, and can be invoked as:

recursivedns on|off         Use the on/off (or yes/no) flag to control whether recursion should be allowed
recursivedns +localhost     Add `localhost' to the list of servers to whom recursion will be allowed
recursivedns -10.10.10.10   Remove `10.10.10.10' from the list of servers allowed to recurse
recursivedns +all           Honor the recurse flag in DNS queries from ALL hosts
recursivedns -all           Deny the recurse flag request in DNS queries from ALL hosts
recursivedns                Display the recursion settings for the local DNS server

Exit Code:                  1 if recursion is allowed, 0 if recursion is denied
Note 1:                     In display mode, one allowed host is printed per line
Note 2:                     Some additional debug messages may be printed on STDERR
Note 3:                     If the program succeeds, the new options are stored in options.conf.wp
                            If the program fails, then the new options are stored in the OPTIONS file
                            These files are located under /etc/bind/

";

use ZoneParser;
use Data::Dumper;
use open ':std';

# returns 1/0 depending upon whether the local dns server allows recursive 
sub allows_recurse () {
    my $zp = new ZoneParser;
    # assume recursion is allowed, for the named syntax says that if recursion not specified, then it's allowed
    my $retval = 1; 

    $zp->parse_file ('/etc/named.conf');
    if ((exists $zp->{options}->{recursion}) and ($zp->{options}->{recursion} eq 0))  {
        $retval = 0;
    }

    return $retval;
}

sub allowed_recursion_hosts () {
    my $zp = new ZoneParser;
    $zp->parse_file ('/etc/named.conf');
    my @recursehosts = ();
    if (exists $zp->{options}->{'allow-recursion'})  {
        die "Unexpected ACL for allow-recursion. Please check your bind configuration" unless (ref $zp->{options}->{'allow-recursion'} eq 'ARRAY');
        for my $singlehostacl (@{$zp->{options}->{'allow-recursion'}}) {
            die "Unexpected sub-ACL in allow-recursion" unless (ref $singlehostacl eq 'ARRAY');
            my @shacl = @{$singlehostacl};
            if ($shacl[1] eq 'acl') { push @recursehosts, $shacl[2]; }
            else { push @recursehosts, $shacl[1]; }
        }
    }
    return @recursehosts;
}

sub display_recursion_settings () {
    my $e = allows_recurse ();
    if ($e eq 0) { print STDERR "(Local DNS server does not allow recursion)\n"; }
    else {
        my @recursehosts = allowed_recursion_hosts ();
        if (scalar (@recursehosts) gt 0) {
            print STDERR "(Local DNS server allows recursion to)\n";
            for my $rh (@recursehosts) { print "$rh\n"; }
        } else {
            print STDERR "(Local DNS server allows recursion to everyone)\n";
        }
    }
    return $e;
}

my $e = 1;

if (grep /^-h|-u|--help|--usage|help|usage$/, @ARGV) { print STDERR $usage; exit 0; }

if ($#ARGV eq -1) { 
    # run without arguments means to just display the recursion settings
    $e = display_recursion_settings ();
}
else {
    # we should allow recursion hosts to be added with +, and hosts to be removed with -, or for recursion to be 
    # toggled on or off using on/off switch
    my $options = undef;
    my $zp = new ZoneParser ();
    $zp->parse_file ('/etc/bind/options.conf.wp');
    die "Malformed options.conf.wp file" unless (ref $zp->{options} eq 'HASH');
    $options = $zp->{options};

    # now start constructing the options file based on the command line options given

    # TODO: We need to extend these options to some more, eventually
    my @recognized_options = ('directory', 'listen-on', 'recursion', 'allow-recursion');

    for my $o (keys %{$options}) {
        die "Unrecognized option $o found in options.conf.wp" unless grep (/^$o$/, @recognized_options);
    }

    # The options are now all recognized, so let us proceed

    open (CF, ">/etc/bind/OPTIONS");
    print CF "// options file auto-generated by recursivedns program\n";
    print CF "\n";
    print CF "options {\n";

    # HANDLE OPTION 'directory'
    if (grep /^directory$/, keys %{$options}) {
        print CF "    directory \"$options->{directory}\";\n";
        delete $options->{directory};
    }

    # HANDLE OPTION 'listen-on'
    if (grep /^listen-on$/, keys %{$options}) {
        my $listenon = $options->{'listen-on'};
        if (ref $listenon eq 'ARRAY') {
            my @listenonvar = @{$listenon};
            print CF "    listen-on ";
            my $bit = shift @listenonvar;
            if ($bit ne undef) { print CF "port $bit ";  } # port is the first variable there 
            print CF "{ ";
            $bit = shift (@listenonvar);
            if ($bit ne undef) {
                if (ref $bit eq 'ARRAY') {
                    # there are some hosts defined here
                    for my $singlehostref (@{$bit}) {
                        if (ref $singlehostref eq 'ARRAY') {
                            if ($singlehostref->[0] eq 1) { print CF "!"; }
                            if ($singlehostref->[1] eq 'acl') { print CF "$singlehostref->[2]; "; }
                            else { print CF "$singlehostref->[1]; "; }
                        }
                    }
                }
            }
            print CF " };\n";
        } else {
            warn "listen-on option was not parsed as an array, hence ignored";
        }

        delete $options->{'listen-on'};
    }

    # SET UP VARIABLES CONTAINING THE CURRENTLY ALLOWED RECURSE HOSTS, THE ONES TO BE ADDED, AND TO BE DELETED
    my @recursehosts = allowed_recursion_hosts ();  # This will re-parse the existing config file
    my @hosts_to_add = map { substr ($_, 1) } grep { $_ =~ /^\+/ } @ARGV;
    my @hosts_to_remove = map { substr ($_, 1) } grep { $_ =~ /^-/ } @ARGV;

    if ( grep (/^-all$/, @ARGV) ) {
        @recursehosts = ();
        @hosts_to_add = ();
        @hosts_to_remove = ();
        $options->{recursion} = 0;
    }

    if ( grep (/^\+all$/, @ARGV) ) {
        @recursehosts = ();
        @hosts_to_add = ();
        @hosts_to_remove = ();
        $options->{recursion} = 1;
    }

    # HANDLE OPTIONS 'recursion' and 'allow-recursion'

    if (grep /^recursion$/, keys %{$options}) {
        my $recursion = $options->{recursion};
        if (grep /^(off|no)$/, @ARGV) { $recursion = 0; }
        if (grep /^(on|yes)$/, @ARGV) { $recursion = 1; }
        if ($recursion) { print CF "    recursion yes;\n"; }
        else { print CF "    recursion no;\n"; }

        delete $options->{recursion};
        $e = $recursion;    # Set the exit code to 1/0 depending on whether recursion is allowed or not
    } else {
        # recursion directive not present. TODO: see what's to be done
        $e = 1;
    }

    my %recursehostkeys =  map { $_ => 1 } @recursehosts, @hosts_to_add;
    @recursehosts = keys %recursehostkeys;

    # @recursehosts = grep { 0 eq scalar (grep /^$_$/, @hosts_to_remove) } @recursehosts;
    @recursehosts = grep { my $x = $_; my @y = grep /^$x$/, @hosts_to_remove; scalar (@y) == 0; } @recursehosts;

    if (scalar @recursehosts gt 0) {
        print CF "    allow-recursion { ";
        for my $rh (@recursehosts) {
            print CF "$rh; ";
        }
        print CF "};\n";
    }

    if (exists $options->{'allow-recursion'}) { delete $options->{'allow-recursion'}; }

    print CF "};\n";
    close (CF);

    if (scalar (keys %{$options})) {
        print STDERR "Some configuration options couldn't be processed: ", join (' ', keys %{$options}), "\n";
    } else {
        system ("/bin/mv -f /etc/bind/OPTIONS /etc/bind/options.conf.wp");
        system ("/etc/init.d/named restart");
    }
}

exit $e;

