#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - autofixer2/unmanaged_zones              Copyright 2021 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

use Cpanel::DNSLib                  ();
use Cpanel::NameServer::Utils::BIND ();
use Cpanel::Config::Users           ();
use Cpanel::Config::LoadCpUserFile  ();
use Cpanel::DnsUtils::AskDnsAdmin   ();
use Cpanel::PwCache                 ();

if ( !-e '/scripts/unmanaged_zones' ) {
    if ( open my $script_fh, '>', '/scripts/unmanaged_zones' ) {
        while ( my $line = <DATA> ) {
            print {$script_fh} $line;
        }
        close $script_fh;
        chmod 0700, '/scripts/unmanaged_zones';
    }
}

my $def_basedir   = '/var/named';
my $def_namedconf = '/etc/named.conf';

# Get chroot settings and DNS user UID and GID
my ( $chrootdir, $binduser, $bindgroup ) = Cpanel::NameServer::Utils::BIND::find_chrootbinddir();
my $dnslib  = Cpanel::DNSLib->new();
my $zonedir = $dnslib->{'data'}{'zonefiledir'};
my $binduid = ( Cpanel::PwCache::getpwnam($binduser) )[2];
my $bindgid = ( getgrnam($bindgroup) )[2];

my ( $live_domains, $dead_domains ) = get_domains_managed_domains();

open( my $ndc_fh, '<', $def_namedconf ) or die "Could not open $def_namedconf for reading: $!\n";
my @conf_zones = readline($ndc_fh);
close $ndc_fh;

my %zones;
my $last_zone_name;
foreach my $line (@conf_zones) {
    if ( $line =~ m/\s*zone\s+["']([\w\-\.]+)["']/ ) {
        next if $1 eq '.';
        my $zone_name = $1;
        next if $zone_name =~ m/^\./;
        next if $zone_name =~ m/\.arpa$/;
        next if ( exists $zones{$zone_name} );    # Seen in another view

        $last_zone_name = $zone_name;
        $zones{$zone_name}{'conf'} = 1;

        if ( -e $def_basedir . '/' . $zone_name . '.db' ) {
            $zones{$zone_name}{'file'} = $def_basedir . '/' . $zone_name . '.db';
        }

        if ($chrootdir) {
            if ( -e $chrootdir . '/' . $def_basedir . '/' . $zone_name . '.db' ) {
                $zones{$zone_name}{'chroot_file'} = $chrootdir . '/' . $def_basedir . '/' . $zone_name . '.db';
            }
        }

        if ( $live_domains->{$zone_name} || $dead_domains->{$zone_name} ) {
            $zones{$zone_name}{'authoritative'} = 1;
        }
        else {
            $zones{$zone_name}{'authoritative'} = 0;
        }
        next;
    }
    elsif ( $last_zone_name && !exists $zones{$last_zone_name}{'file'} && $line =~ m/file\s+['"]([^'"]+)/ ) {
        if ( -e $1 ) {
            $zones{$last_zone_name}{'file'} = $1;
        }
        else {
            undef $last_zone_name;
        }
        next;
    }
}

if ( opendir my $zone_dh, $def_basedir ) {
    while ( my $file = readdir $zone_dh ) {
        next if $file !~ m/(.+)\.db$/;    # Only look at cPanel managed zone file
        my $zone_name = $1;
        next if $zone_name =~ m/\.arpa$/;
        next if exists $zones{$zone_name};
        $zones{$zone_name}{'conf'} = 0;
        $zones{$zone_name}{'file'} = $def_basedir . '/' . $file;
        if ( $live_domains->{$zone_name} || $dead_domains->{$zone_name} ) {
            $zones{$zone_name}{'authoritative'} = 1;
        }
        else {
            $zones{$zone_name}{'authoritative'} = 0;
        }
    }
    closedir $zone_dh;
}

my %unmanaged_zones;
my $has_unmanaged_zones = -e '/etc/unmanaged_zones' ? 1 : 0;
if ($has_unmanaged_zones) {
    if ( open my $unmanaged_fh, '<', '/etc/unmanaged_zones' ) {
        while ( my $line = readline $unmanaged_fh ) {
            $line =~ s/^\s+//;
            $line =~ s/\s+$//;    # no need to chomp
            next if ( $live_domains->{$line} || $dead_domains->{$line} );
            $unmanaged_zones{$line} = 1;
        }
    }
}

my @missing_zone_files;
my @missing_conf_entries;
my @remove_zones;
foreach my $zone ( sort keys %zones ) {
    if ( !$zones{$zone}{'file'} ) {
        push @missing_zone_files, $zone;
    }

    if ( !$zones{$zone}{'conf'} ) {
        push @missing_conf_entries, "$zone ($zones{$zone}{'file'})";
    }

    if ( !$zones{$zone}{'authoritative'} ) {
        if ( $has_unmanaged_zones && !$unmanaged_zones{$zone} ) {
            push @remove_zones, $zone;
        }
        else {
            $unmanaged_zones{$zone} = 1;
        }
    }
}

if ( @remove_zones || scalar keys %unmanaged_zones ) {
    print "The following zones are not directly managed by this server:\n";
    print join( "\n", sort keys %unmanaged_zones ) . "\n";
    print join( "\n", @remove_zones ) . "\n\n";
}

if (@missing_conf_entries) {
    print "The following zone files do not have a corresponding entry in $def_namedconf:\n";
    print join( "\n", @missing_conf_entries ) . "\n\n";
}

if (@missing_zone_files) {
    print "The following zone entries found in $def_namedconf do not have a corresponding zone file:\n";
    print join( "\n", @missing_zone_files ) . "\n\n";
}

if ( open my $unmanaged_fh, '>', '/etc/unmanaged_zones' ) {
    print {$unmanaged_fh} join( "\n", sort keys %unmanaged_zones ) . "\n";
    close $unmanaged_fh;
}

if ( !$has_unmanaged_zones ) {
    print "\n\n";
    print "Writing unmanaged/unauthoritative domains to /etc/unmanaged_zones\n";
    print "To remove any unmanaged domains from this system please edit /etc/unmanaged_zones\n";
    print "and remove any domains that you wish to have removed and run /scripts/unmanaged_zones.\n";
    print "Zones added through the WHM directly will be included in this list. Please review the zones\n";
    print "listed in the file carefully and ensure that they should be removed before deleting their\n";
    print "respective entries in /etc/unmanaged_zones.\n";
    sleep 10 if -t STDIN;
    print "Done\n";
    exit;
}

if ( !-t STDIN ) {
    exit;
}

if (@remove_zones) {
    print "The following zones are subject to removal:\n";
    print join( ', ', @remove_zones ) . "\n";
    print "Would you like to remove the zone files and entries in $def_namedconf for the unauthoritative zones on this server only? (y/n)  ";
    my $answer = <STDIN>;
    if ( $answer !~ m/^y/i ) {
        exit;
    }
    else {
        my $count = scalar @remove_zones;
        print "Will remove $count zones\n";
        sleep 3;
        print "Removing zones ...\n";
    }
    foreach my $zone (@remove_zones) {
        removezonelocal($zone);
    }
    print "Done.\n";
}
else {
    print "\nAll zones accounted for and managed by cPanel/WHM. See /etc/unmanaged_zones for exclusions.\n";
}

sub get_domains_managed_domains {
    my %live_domains;
    my %dead_domains;
    foreach my $user ( Cpanel::Config::Users::getcpusers() ) {

        # Safe because it's only loading from cpusers list.
        my $cpuser_ref = Cpanel::Config::LoadCpUserFile::loadcpuserfile( $user, 1 );

        if ( $cpuser_ref->{'DOMAIN'} ) {
            $live_domains{ $cpuser_ref->{'DOMAIN'} } = 1;
        }
        if ( ref $cpuser_ref->{'DOMAINS'} eq 'ARRAY' ) {
            my $_tmp = $cpuser_ref->{'DOMAINS'};
            map { $live_domains{$_} = 1 } @$_tmp;
        }

        if ( ref $cpuser_ref->{'DEADDOMAINS'} eq 'ARRAY' ) {
            my $_tmp = $cpuser_ref->{'DEADDOMAINS'};
            map { $dead_domains{$_} = 1 } @$_tmp;
        }
    }
    return ( \%live_domains, \%dead_domains );
}

sub removezonelocal {
    print Cpanel::DnsUtils::AskDnsAdmin::askdnsadmin( 'REMOVEZONE', 1, $_[0] );
    return;
}
__END__
#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - autofixer2/unmanaged_zones              Copyright 2021 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

use Cpanel::DNSLib                  ();
use Cpanel::NameServer::Utils::BIND ();
use Cpanel::Config::Users           ();
use Cpanel::Config::LoadCpUserFile  ();
use Cpanel::DnsUtils::AskDnsAdmin   ();
use Cpanel::PwCache                 ();

my $def_basedir   = '/var/named';
my $def_namedconf = '/etc/named.conf';

# Get chroot settings and DNS user UID and GID
my ( $chrootdir, $binduser, $bindgroup ) = Cpanel::NameServer::Utils::BIND::find_chrootbinddir();
my $dnslib  = Cpanel::DNSLib->new();
my $zonedir = $dnslib->{'data'}{'zonefiledir'};
my $binduid = ( Cpanel::PwCache::getpwnam($binduser) )[2];
my $bindgid = ( getgrnam($bindgroup) )[2];

my ( $live_domains, $dead_domains ) = get_domains_managed_domains();

open( my $ndc_fh, '<', $def_namedconf ) or die "Could not open $def_namedconf for reading: $!\n";
my @conf_zones = readline($ndc_fh);
close $ndc_fh;

my %zones;
my $last_zone_name;
foreach my $line (@conf_zones) {
    if ( $line =~ m/\s*zone\s+["']([\w\-\.]+)["']/ ) {
        next if $1 eq '.';
        my $zone_name = $1;
        next if $zone_name =~ m/^\./;
        next if $zone_name =~ m/\.arpa$/;
        next if ( exists $zones{$zone_name} );    # Seen in another view

        $last_zone_name = $zone_name;
        $zones{$zone_name}{'conf'} = 1;

        if ( -e $def_basedir . '/' . $zone_name . '.db' ) {
            $zones{$zone_name}{'file'} = $def_basedir . '/' . $zone_name . '.db';
        }

        if ($chrootdir) {
            if ( -e $chrootdir . '/' . $def_basedir . '/' . $zone_name . '.db' ) {
                $zones{$zone_name}{'chroot_file'} = $chrootdir . '/' . $def_basedir . '/' . $zone_name . '.db';
            }
        }

        if ( $live_domains->{$zone_name} || $dead_domains->{$zone_name} ) {
            $zones{$zone_name}{'authoritative'} = 1;
        }
        else {
            $zones{$zone_name}{'authoritative'} = 0;
        }
        next;
    }
    elsif ( $last_zone_name && !exists $zones{$last_zone_name}{'file'} && $line =~ m/file\s+['"]([^'"]+)/ ) {
        if ( -e $1 ) {
            $zones{$last_zone_name}{'file'} = $1;
        }
        else {
            undef $last_zone_name;
        }
        next;
    }
}

if ( opendir my $zone_dh, $def_basedir ) {
    while ( my $file = readdir $zone_dh ) {
        next if $file !~ m/(.+)\.db$/;    # Only look at cPanel managed zone file
        my $zone_name = $1;
        next if $zone_name =~ m/\.arpa$/;
        next if exists $zones{$zone_name};
        $zones{$zone_name}{'conf'} = 0;
        $zones{$zone_name}{'file'} = $def_basedir . '/' . $file;
        if ( $live_domains->{$zone_name} || $dead_domains->{$zone_name} ) {
            $zones{$zone_name}{'authoritative'} = 1;
        }
        else {
            $zones{$zone_name}{'authoritative'} = 0;
        }
    }
    closedir $zone_dh;
}

my %unmanaged_zones;
my $has_unmanaged_zones = -e '/etc/unmanaged_zones' ? 1 : 0;
if ($has_unmanaged_zones) {
    if ( open my $unmanaged_fh, '<', '/etc/unmanaged_zones' ) {
        while ( my $line = readline $unmanaged_fh ) {
            $line =~ s/^\s+//;
            $line =~ s/\s+$//;    # no need to chomp
            next if ( $live_domains->{$line} || $dead_domains->{$line} );
            $unmanaged_zones{$line} = 1;
        }
    }
}

my @missing_zone_files;
my @missing_conf_entries;
my @remove_zones;
foreach my $zone ( sort keys %zones ) {
    if ( !$zones{$zone}{'file'} ) {
        push @missing_zone_files, $zone;
    }

    if ( !$zones{$zone}{'conf'} ) {
        push @missing_conf_entries, "$zone ($zones{$zone}{'file'})";
    }

    if ( !$zones{$zone}{'authoritative'} ) {
        if ( $has_unmanaged_zones && !$unmanaged_zones{$zone} ) {
            push @remove_zones, $zone;
        }
        else {
            $unmanaged_zones{$zone} = 1;
        }
    }
}

if ( @remove_zones || scalar keys %unmanaged_zones ) {
    print "The following zones are not directly managed by this server:\n";
    print join( "\n", sort keys %unmanaged_zones ) . "\n";
    print join( "\n", @remove_zones ) . "\n\n";
}

if (@missing_conf_entries) {
    print "The following zone files do not have a corresponding entry in $def_namedconf:\n";
    print join( "\n", @missing_conf_entries ) . "\n\n";
}

if (@missing_zone_files) {
    print "The following zone entries found in $def_namedconf do not have a corresponding zone file:\n";
    print join( "\n", @missing_zone_files ) . "\n\n";
}

if ( open my $unmanaged_fh, '>', '/etc/unmanaged_zones' ) {
    print {$unmanaged_fh} join( "\n", sort keys %unmanaged_zones ) . "\n";
    close $unmanaged_fh;
}

if ( !$has_unmanaged_zones ) {
    print "\n\n";
    print "Writing unmanaged/unauthoritative domains to /etc/unmanaged_zones\n";
    print "To remove any unmanaged domains from this system please edit /etc/unmanaged_zones\n";
    print "and remove any domains that you wish to have removed and run /scripts/unmanaged_zones.\n";
    print "Zones added through the WHM directly will be included in this list. Please review the zones\n";
    print "listed in the file carefully and ensure that they should be removed before deleting their\n";
    print "respective entries in /etc/unmanaged_zones.\n";
    sleep 10 if -t STDIN;
    print "Done\n";
    exit;
}

if ( !-t STDIN ) {
    exit;
}

if (@remove_zones) {
    print "The following zones are subject to removal:\n";
    print join( ', ', @remove_zones ) . "\n";
    print "Would you like to remove the zone files and entries in $def_namedconf for the unauthoritative zones on this server only? (y/n)  ";
    my $answer = <STDIN>;
    if ( $answer !~ m/^y/i ) {
        exit;
    }
    else {
        my $count = scalar @remove_zones;
        print "Will remove $count zones\n";
        sleep 3;
        print "Removing zones ...\n";
    }
    foreach my $zone (@remove_zones) {
        removezonelocal($zone);
    }
    print "Done.\n";
}
else {
    print "\nAll zones accounted for and managed by cPanel/WHM. See /etc/unmanaged_zones for exclusions.\n";
}

sub get_domains_managed_domains {
    my %live_domains;
    my %dead_domains;
    foreach my $user ( Cpanel::Config::Users::getcpusers() ) {

        # Safe because it's only loading from cpusers list.
        my $cpuser_ref = Cpanel::Config::LoadCpUserFile::loadcpuserfile( $user, 1 );

        if ( $cpuser_ref->{'DOMAIN'} ) {
            $live_domains{ $cpuser_ref->{'DOMAIN'} } = 1;
        }
        if ( ref $cpuser_ref->{'DOMAINS'} eq 'ARRAY' ) {
            my $_tmp = $cpuser_ref->{'DOMAINS'};
            map { $live_domains{$_} = 1 } @$_tmp;
        }

        if ( ref $cpuser_ref->{'DEADDOMAINS'} eq 'ARRAY' ) {
            my $_tmp = $cpuser_ref->{'DEADDOMAINS'};
            map { $dead_domains{$_} = 1 } @$_tmp;
        }
    }
    return ( \%live_domains, \%dead_domains );
}

sub removezonelocal {
    print Cpanel::DnsUtils::AskDnsAdmin::askdnsadmin( 'REMOVEZONE', 1, $_[0] );
    return;
}
