ACC SHELL

Path : /usr/share/bind/
File Upload :
Current File : //usr/share/bind/ldapdump

#!/usr/bin/perl -w

#
# script for dumping LDAP data into zone files with
# keeping dhcpd added hosts.
#
# LDAP zones that are not existing in /etc/named.conf
# are created as static zones there. The zone files are
# created too under $namedDir/master/$zonename
#
# Zones that are existing as file but not in LDAP are
# dropped from /etc/named.conf and the zone files are
# deleted
#
# The nameserver will not be reloaded. You have to do this
# on your own.
#
# if you have dynamic zones, you need the nsupdate binary from
# the bind-utils package.
#
# Uwe Gansert ug@suse.de
# Copyright 2004, Novell, Inc.  All rights reserved.
#

use strict;
eval("use Net::LDAP;");
if( $@ ) {
    print STDERR "\n  Loading the perl module Net::LDAP failed\n";
    print STDERR "  please install the perl-ldap RPM.\n\n";
    print STDERR "$@\n";
    exit(101);
}

# $zones{$zonename} = { allow-update => keyname, type => master, file => master/bal.de }
my %zones;

my $DEBUG        = 0;
my $DONSUPDATE   = 1;
my $DOFILES      = 1;
my $dryrun       = 0;
my $server       = "127.0.0.1";
my $ldapServer   = undef;
my $ldapPort     = 389;
my $ldapBaseDN   = undef;
my $ldapBindDN   = undef;
my $ldapPassword = undef;
my $namedDir     = getNamedDir();
my $help         = undef;
my $nsupdate     = "/usr/bin/nsupdate";
my $dig          = "/usr/bin/dig";
my $base         = undef;
my $namedRunning = statusNamed();
my $version      = 1.2;
my $keydir       = '/etc/named.d/';

die("no namedDir found in /etc/named.conf") unless $namedDir;

use Getopt::Long;
GetOptions (
            "z"   => \$DEBUG,
            "y"   => \$dryrun,
            "s=s" => \$server,
            "l=s" => \$ldapServer,
            "p=s" => \$ldapPort,
            "b=s" => \$ldapBaseDN,
            "D=s" => \$ldapBindDN,
            "e=s" => \$base,
            "w=s" => \$ldapPassword,
            "n=s" => \$nsupdate,
            "m=s" => \$dig,
            "k=s" => \$keydir,
            "h|help"   => \$help
           );

if( $help ) {
    print STDERR "\n";
    print STDERR "-s <nameserver> => which nameserver to use [$server]\n";
    print STDERR "-l <ldapserver> => which ldap server to use [HOST from /etc/openldap/ldap.conf]\n";
    print STDERR "-p <ldapport>   => which ldap port to use   [PORT from /etc/openldap/ldap.conf]\n";
    print STDERR "-b <ldapbasedn> => which ldap basedn to use [BASE from /etc/openldap/ldap.conf]\n";
    print STDERR "-w <ldappasswd> => which ldap admin password to use\n";
    print STDERR "-D <ldapbinddn> => the ldap bind dn\n";
    print STDERR "-e <ldapDNSdn>  => the DN where the DNS data is stored [from suseDnsConfiguration object]\n";
    print STDERR "-m <dig>        => dig binary [$dig]\n";
    print STDERR "-k <keydir>     => directory that contains the DDNS keys [$keydir]\n";
    print STDERR "-n <nsupdate>   => nsupdate binary [$nsupdate]\n";
    print STDERR "-z              => turns on debugging\n";
    print STDERR "-y              => dry run (nothing will be changed in any files)\n";
    print STDERR "-h              => this help\n";
    print STDERR "\n";
    print STDERR "Example:\n";
    print STDERR "$0 -s 127.0.0.1 -l localhost -p 389 -D uid=root,dc=suse,dc=de -b dc=suse,dc=de -w MyPaSs -z\n";
    print STDERR "$0 -y -z # dry run with debug output\n";
    print STDERR "\n";
    exit;
}

if( $dryrun ) {
    $DONSUPDATE = 0;
    $DOFILES    = 0;
}

$ldapServer = getLDAPConfigServer() if( not $ldapServer );
$ldapBaseDN = getLDAPConfigBaseDN() if( not $ldapBaseDN );

if( $DEBUG ) {
    print STDERR "ldapServer = $ldapServer\n";
    print STDERR "ldapPort   = $ldapPort\n";
    print STDERR "ldapBaseDN = $ldapBaseDN\n";
    print STDERR "named dir  = $namedDir\n";
    print STDERR "named run  = $namedRunning\n";
}

my $ldap= Net::LDAP->new($ldapServer, port => $ldapPort, version => 3);
if(!defined $ldap) {
    error("can't contact LDAP Server");
    exit -1;
}

my $mesg;
if( $ldapBindDN ) {
    $mesg=$ldap->bind($ldapBindDN, password=>$ldapPassword);
} else {
    $mesg=$ldap->bind();
}
if($mesg->code != 0) {
    my $text = "bind failed\n";
    error($text);
    exit(-1);
}


$base = getLDAPConfigDNSBaseDN() unless( $base );
if( not $base ) {
    print STDERR "can't find LDAP DNS configuration base\n";
    exit(-100);
}

if( $DEBUG ) {
    print STDERR "DNS DN     = $base\n\n";
}

# read dynamic and static zoneinfos from named.conf
print STDERR "********** CHECKPOINT : get named.conf zones\n" if($DEBUG);
getZoneTypes();
if( $DEBUG ) {
    use Data::Dumper;
    print STDERR Data::Dumper->Dump( [ \%zones ] );
}

# drop static zone entries in dynamic zones via nsupdate
# first all forward zones, then all reverse zones
print STDERR "********** CHECKPOINT : drop static zone entries in dynamic zones\n" if($DEBUG);
foreach my $zone ( sortZones() ) {
    next unless $namedRunning;
    next unless $zones{$zone}->{"allow-update"};
    next if( $zones{$zone}->{type} eq 'slave' );
    next unless( -f $namedDir."/".$zones{$zone}->{file} );
    print STDERR "\tdropStaticZoneEntries($zone)\n" if($DEBUG);
    dropStaticZoneEntries($zone);
}

# drop all static zones by "hand"
print STDERR "********** CHECKPOINT : drop all static zones\n" if($DEBUG);
foreach my $zone ( keys(%zones) ) {
    next if $zones{$zone}->{"allow-update"};
    next if( $zones{$zone}->{type} eq 'slave' );
    print STDERR "\tdropZone($zone)\n" if($DEBUG);
    dropZone($zone);
    delete( $zones{$zone} );
}

# we have just cleaned dynamic zones now.
# it's time to merge the LDAP data now

# drop all dynamic zones that are not in LDAP
print STDERR "********** CHECKPOINT : drop all dynamic zones that are not in LDAP\n" if($DEBUG);
foreach my $zone ( keys(%zones) ) {
    next if( $zones{$zone}->{type} eq 'slave' );
    $mesg = $ldap->search(
                          base => $base,
                          scope => 'sub',
                          filter => "(&(zoneName=$zone)(objectclass=dNSZone))"
                         );
    if( $mesg->code != 0 ){
        error("LDAP: search failed\n");
        $ldap->unbind;
        exit(-3);
    } elsif( $mesg->count == 0) {
        # a dynamic zone has been dropped from LDAP but is still
        # existing in config. So we remove it now from the config
        print STDERR "\tdropZone($zone)\n" if($DEBUG);
        dropZone($zone);
        delete( $zones{$zone} );
    }
}

# create new zones in LDAP as files now
$mesg = $ldap->search(
                      base => $base,
                      scope => 'one',
                      filter => '(objectclass=dNSZone)'
                     );
if( $mesg->code != 0 ) {
    error("LDAP: search failed\n");
    $ldap->unbind;
    exit(-3);
}

print STDERR "********** CHECKPOINT : create Zones that are new in LDAP\n" if($DEBUG);
foreach my $zone_entry ( $mesg->entries ) {
    my $dn = $zone_entry->dn();
    my ( $zone ) = $dn =~ /^zoneName=([^,]+)/i;
    if( not $namedRunning ) {
        createStaticZone( $dn ); # special case. named not running, dyn. zones gets recreated
        next;
    }
    next if( exists($zones{$zone}) and -f $namedDir."/".$zones{$zone}->{file} );
    print STDERR "\tcreateStaticZone( $dn )\n" if($DEBUG);
    createStaticZone( $dn );
}

# add the new entries for dynamic zones now
print STDERR "********** CHECKPOINT : add the new entries for dynamic zones now\n" if($DEBUG);
foreach my $zone_entry ( $mesg->entries ) {
    next unless( $namedRunning );
    my $dn = $zone_entry->dn();
    my ( $zone ) = $dn =~ /^zoneName=([^,]+)/i;
    next if( not exists($zones{$zone}) );
    print STDERR "\tupdateDynamicZone( $dn )\n" if($DEBUG);
    updateDynamicZone( $dn );
}




# FIXME: not so rock solid parser
# should be more sophisticated
sub getZoneTypes {
    open(FILE, "< /etc/named.conf") or die("can't open /etc/named.conf for reading. $!");
    while( my $l = <FILE> ) {
        next unless $l =~ /^zone\s+\"*([^\s"]+)\"*/;
        my $zone = $1;
        my $bc = $l =~ tr/{/{/;
        $zones{$zone} = { 'allow-update' => 0 };
        while( $l = <FILE> ) {
            $l =~ s/#.*//g;
            next if $l =~ /^\s*$/;
            $bc += $l =~ tr/{/{/;
            if( $l =~ /^\s*allow-update/ ) {
                # catches allow-update { key ...
                if( $l =~ /key\s+\"*([^\s;"]+)\"*/ ) {
                    $zones{$zone}->{'allow-update'} = $1;
                    my $keyfile;
                    eval("\$keyfile = <$keydir/K".lc($zones{$zone}->{'allow-update'})."*>");
                    if( ! $keyfile ) {
                        die( "unable to read keyfile for ".$zones{$zone}->{'allow-update'}." in $keydir\n" );
                    }
                } else {
                    # catches allow-update {
                    #                  key ....
                    while( $l = <FILE> ) {
                        $l =~ s/#.*//g;
                        next unless $l =~ /key\s+\"*([^\s;"]+)\"*/;
                        $zones{$zone}->{'allow-update'} = $1;
                        last;
                    }
                }
            } elsif( $l =~ /^\s*file\s+(.*)/ ) {
                my $file = $1;
                $file =~ s/^"//g;
                $file =~ s/\s*;\s*$//;
                $file =~ s/"$//g;
                $zones{$zone}->{'file'} = $file;
            } elsif( $l =~ /^\s*type\s+\"*([^\s";]+)\"*/ ) {
                $zones{$zone}->{'type'} = $1;
            }
            $bc -= $l =~ tr/}/}/;
            last if not $bc;
        }
        if( $zones{$zone}->{type} eq 'hint' ) {
            delete($zones{$zone});
        } elsif( $zone eq 'localhost' ) {
            delete($zones{$zone});
        } elsif( $zone eq '0.0.127.in-addr.arpa' ) {
            delete($zones{$zone});
        }
    }
    close(FILE);
}

my %keepRRinReverse = ();
sub dropStaticZoneEntries {
    my $zone = shift;
    print STDERR "\t\tdigging: $dig \@$server -t AXFR $zone\n" if($DEBUG);
    my $data = `$dig \@$server -t AXFR $zone`;
    my @data = split( "\n", $data );

    my %toDel;
    foreach my $l ( @data ) {
        if( $l =~ /\s+IN\s+(NS|A|MX|PTR|CNAME|SRV|HINFO|MINFO|SIG|KEY|AAAA|LOC|NXT|NAPTR|KX|CERT|A6|DNAME)\s+/ ) {
            my $rr = $1;
            $l =~ /^(\S+)/;
            my $e = $1;
            if( $zone !~ /in-addr\.arpa/ and grep( { /^$e\s+.*"[0-9a-f]{34}"$/i } @data ) ) {
                # this happens only in forward zones. Dynamic reverse zone RRs have no TXT record
                print STDERR "\t\twill not drop entry $e because of TXT record\n" if($DEBUG);
		if( $rr eq 'A' ) {
			$l =~ /([\d.]+)$/;
			my $ip = $1;
			$ip = join( '.', reverse( split( /\./, $ip ) ) );
			$ip .= ".in-addr.arpa.";
	                $keepRRinReverse{$ip} = 1;
			print STDERR "\t\tremember '$ip' to keep\n";
		}
            } elsif( $zone =~ /^([\d.]+)in-addr\.arpa/ ) {
		if( not exists($keepRRinReverse{$e}) ) {
                    $toDel{$e} = 1;
                    print STDERR "\t\twill drop entry $e $rr\n" if($DEBUG);
                } else {
                    # this looks like a dynamic reverse zone entry
                    print STDERR "\t\twill not drop entry $e in reverse zone because of a TXT record in forward zone\n"
                        if($DEBUG);
                }
            } else {
                $toDel{$e} = 1;
                print STDERR "\t\twill drop entry $e $rr\n" if($DEBUG);
            }
        } else {
            # debug?
        }
    }
    $SIG{PIPE} = 'IGNORE';
    if( $DONSUPDATE ) {
        my $keyfile;
        eval("\$keyfile = <$keydir/K".lc($zones{$zone}->{'allow-update'})."*>");
        $keyfile =~ s/\.[^.]+$//g;
        print STDERR "\t\tusing key ".lc($zones{$zone}->{'allow-update'})." for zone $zone\n" if( $DEBUG );
        print STDERR "\t\topen pipe to: nsupdate -k $keyfile\n" if($DEBUG);
        open( PIPE, "|$nsupdate -k $keyfile > /dev/null" ) or do {
            print STDERR "unable to open pipe to $nsupdate -k $keyfile\n";
            die($!);
        };
        print PIPE "server $server\n" or die "can’t write to $nsupdate pipe: $!";
    }
    print STDERR "\t\tupdate add $zone. 1234 NS ldapdump_kill_me\n" if( $DEBUG );
    if( $DONSUPDATE ) {
        # create dummy NS record
        # sadly this one is needed if we want to change the last NS record
        print PIPE "update add $zone. 1234 NS ldapdump_kill_me\n" or die "can’t write to $nsupdate pipe: $!";
    }
    foreach my $e ( @data ) {
        next if( $e =~ /^[\s;]/ );
        next if( $e =~ /^$/ );
        $e =~ /^(\S+).*\s+IN\s+(\w+)\s+(.*)/;
        if( exists($toDel{$1}) ) {
            print STDERR "\t\tupdate delete $1 $2 $3\n" if( $DEBUG );
            if( $DONSUPDATE ) {
                print PIPE "update delete $1 $2 $3\n" or die "can’t write to $nsupdate pipe: $!";
            }
        }
    }
    if( $DONSUPDATE ) {
        print PIPE "\n\n" or die "can’t write to $nsupdate pipe: $!";
        close(PIPE) or die "can’t close $nsupdate pipe: status=$?";
    }
    print STDERR "\n\t\tpipe to $nsupdate closed\n" if($DEBUG);
    return 1;
}

# remove zone entry from named.conf and delete zone file
sub dropZone {
    my $zone = shift;

    open(FILE, "< /etc/named.conf") or die("reading named.conf $!");
    my @named_conf = <FILE>;
    close(FILE);
    for( my $i=0; $i < @named_conf; $i++ ) {
        next unless $named_conf[$i] =~ /^zone\s+\"*$zone/;
        print STDERR "\t\tfound zone $zone in named.conf. Dropping ...\n" if($DEBUG);
        my $bc = $named_conf[$i] =~ tr/{/{/;
        $bc -= $named_conf[$i] =~ tr/}/}/;
        $named_conf[$i++] = "";
        do {
            $named_conf[$i] =~ s/#.*//g;
            $bc += $named_conf[$i] =~ tr/{/{/;
            $bc -= $named_conf[$i] =~ tr/}/}/;
            $named_conf[$i++] = "";
        } while( $bc and ($i < @named_conf) );
        print STDERR " done\n" if($DEBUG);
    }
    if( $DOFILES ) {
        open(FILE, "> /etc/named.conf") or die("writing named.conf $!");
        print FILE @named_conf;
        close(FILE);
    }
    print STDERR "\t\tnamed.conf written\n" if($DEBUG);
    my $del = $namedDir.'/'.$zones{$zone}->{file}.'*';
    if( $DOFILES ) {
        eval "unlink <$del>";
    }
    print STDERR "\t\tunlink: $del\n" if($DEBUG);
}

sub error {
    my $t = shift;

    print STDERR "$t\n";
}

# this one writes zone files and modifies the
# named.conf
sub createStaticZone {
    my $dn = shift;
    my @outf = ();
    my $zone;

    my $mesg = $ldap->search(
                      base => $dn,
                      scope => 'base',
                      filter => '(&(relativeDomainName=@)(objectclass=dNSZone))'
                     );
    if( $mesg->code != 0 ) {
        error("LDAP: search failed\n");
        $ldap->unbind;
        exit(-3);
    }

    foreach my $zone_entry ( $mesg->entries ) {
        $zone = $zone_entry->get_value('zoneName');
        print STDERR "\t\tcreating static zone $zone as file\n" if($DEBUG);
        my $ttl  = $zone_entry->get_value('dnsttl');
        my $soa = $zone_entry->get_value('soarecord');
        my @soa = ( '', '' );
        @soa = split(/ /,$soa) if( $soa );
        push @outf, "\$TTL $ttl\n";
        push @outf, "\@\tIN\tSOA\t".$soa[0]." ".$soa[1]." (\n";
        for( my $i=2; $i < @soa; $i++ ) {
            push @outf, "\t\t\t".$soa[$i]."\n";
        }
        chomp($outf[-1]);
        $outf[-1] .= ")\n";
        foreach my $rec ( qw( NS A MX PTR CNAME SRV HINFO MINFO SIG KEY AAAA LOC NXT NAPTR KX CERT A6 DNAME ) ) {
            my $ref = $zone_entry->get_value($rec.'record', asref => 1);
            next unless $ref;
            foreach my $rr ( @$ref ) {
                print STDERR "\t\tadding RR $rec $rr.\n" if($DEBUG);
                my $str = "\tIN\t$rec\t".$rr;
                push @outf, "$str\n";
            }
        }
    }

    $mesg = $ldap->search(
                          base => $dn,
                          scope => 'one',
                          filter => '(objectclass=dNSZone)'
                     );
    if( $mesg->code != 0 ) {
        error("LDAP: search failed\n");
        $ldap->unbind;
        exit(-3);
    }
    foreach my $zone_entry ( $mesg->entries ) {
        my $ttl  = $zone_entry->get_value('dnsttl');
        my $rdn  = $zone_entry->get_value('relativedomainname');
        my $zone = $zone_entry->get_value('zoneName');
        foreach my $rec ( qw( NS A PTR MX TXT CNAME SRV HINFO MINFO SIG KEY AAAA LOC NXT NAPTR KX CERT A6 DNAME ) ) {
            my $ref = $zone_entry->get_value($rec.'record', asref => 1);
            next unless $ref;
            foreach my $rr ( @$ref ) {
                print STDERR "\t\tadding RR $rec $rr\n" if($DEBUG);
                push @outf, "$rdn\tIN\t$rec\t$rr\n";
            }
        }
    }
#    my $filename = $namedDir."/";
    my $filename = "";
    my $doChown = 0;
    if( exists($zones{$zone} ) ) {
        # this is a not so nice workaround
        # if the nameserver is not running, dynamic entries which are
        # not in LDAP get lost during this. To avoid this, I have
        # to parse existing dynamic zone file and save the TXT records from
        # not getting written.
        # 
        $filename .= $zones{$zone}->{file};
        push( @outf, parseDynEntries($namedDir."/".$filename) );
        delete($zones{$zone});
        $doChown = 1;
        my $del = $namedDir."/".$filename.'*';
        print STDERR "\t\tdeleting file from dynamic zone $del\n" if( $DEBUG );
        if( $DOFILES ) {
            eval "unlink <$del>";
        }
        print STDERR "\t\tcreating file for new dynamic zone $zone ($filename)\n" if( $DEBUG );
    } else {
        $filename .= "master/$zone";
        open( FILE, "< /etc/named.conf" ) or die "unable to read /etc/named.conf. $!";
        my @file = <FILE>;
        close(FILE);

        print STDERR "\t\tadding zone \"$zone\" to named.conf\n" if($DEBUG);
        push( @file, "zone \"$zone\" in {\n" );
        push( @file, "\tfile \"$filename\";\n" );
        push( @file, "\ttype master;\n" );
        push( @file, "};\n" );

        if( $DOFILES ) {
            open( FILE, "> /etc/named.conf" ) or die "unable to write /etc/named.conf. $!";
            print FILE @file;
            close(FILE);
        }
    }
    print STDERR "\t\tcreating file: $filename\n" if($DEBUG);
    if( $DOFILES ) {
        open( FILE, "> $namedDir/$filename" );
        print FILE @outf;
        close(FILE);
        if( $doChown ) {
            my ($login,$pass,$uid,$gid) = getpwnam('named');
            print STDERR "\t\tchowning file: $uid, $gid, $filename\n" if( $DEBUG );
            chown( $uid, $gid, $filename ); 
        }
    }


    return;
}

# this one uses nsupdate to add new entries to dynamic zones
#
sub updateDynamicZone {
    my $dn = shift;

    my $mesg = $ldap->search(
                      base => $dn,
                      scope => 'base',
                      filter => '(&(relativeDomainName=@)(objectclass=dNSZone))'
                     );
    if( $mesg->code != 0 ) {
        error("LDAP: search failed\n");
        $ldap->unbind;
        exit(-3);
    }

    my $ttl;
    my $zone;
    foreach my $zone_entry ( $mesg->entries ) {
        $zone = $zone_entry->get_value('zoneName');
        $ttl  = $zone_entry->get_value('dnsttl');
    }

    $SIG{PIPE} = 'IGNORE';
    if( $DONSUPDATE ) {
        my $keyfile;
        eval("\$keyfile = <$keydir/K".lc($zones{$zone}->{'allow-update'})."*>");
        $keyfile =~ s/\.[^.]+$//g;
        print STDERR "\t\tusing key ".lc($zones{$zone}->{'allow-update'})." for zone $zone\n" if( $DEBUG );
        print STDERR "\t\topen pipe to: $nsupdate -k $keyfile\n" if($DEBUG);
        open( PIPE, "|$nsupdate -k $keyfile > /dev/null" ) or do {
            print STDERR "unable to open pipe to $nsupdate -k $keyfile\n";
            die($!);
        };
    }


    $mesg = $ldap->search(
                          base => $dn,
                          scope => 'sub',
                          filter => '(objectclass=dNSZone)'
                     );
    if( $mesg->code != 0 ) {
        error("LDAP: search failed\n");
        $ldap->unbind;
        exit(-3);
    }
    print STDERR "\t\tcreating dyn. zone from $dn\n" if($DEBUG);
    if( $DONSUPDATE ) {
        print PIPE "server $server\n" or die "can’t write to $nsupdate pipe: $!";
    }
    foreach my $zone_entry ( $mesg->entries ) {
        my $ttl  = $zone_entry->get_value('dnsttl') || $ttl;
        my $rdn  = $zone_entry->get_value('relativedomainname');
        my $zone = $zone_entry->get_value('zoneName') || $zone;
        foreach my $rec ( qw( SOA NS A PTR MX TXT CNAME SRV HINFO MINFO SIG KEY AAAA LOC NXT NAPTR KX CERT A6 DNAME ) ) {
            my $ref = $zone_entry->get_value($rec.'record', asref => 1);
            next unless $ref;
            foreach my $rr ( @$ref ) {
                my $where = ($rdn eq '@')?("$zone."):("$rdn.$zone");
                my $command = "update add $where $ttl $rec $rr\n";
                print STDERR "\t\t$command" if($DEBUG);
                if( $DONSUPDATE ) {
                    print PIPE "$command" or die "can’t write to $nsupdate pipe: $!";
                }
            }
        }
    }
    print STDERR "\t\tupdate delete $zone. NS ldapdump_kill_me\n" if($DEBUG);
    if( $DONSUPDATE ) {
        print PIPE "update delete $zone. NS ldapdump_kill_me\n" or die "can’t write to $nsupdate pipe: $!";
        print PIPE "\n\n\n" or die "can’t write to $nsupdate pipe: $!";
        close(PIPE) or die "can’t close $nsupdate pipe: status=$?";
    }
    return;
}

sub getNamedDir {
    my $ret;
    open( FILE, "< /etc/named.conf" ) or die "unable to open named.conf. $!";
    while(<FILE>) {
        next unless /^options/;
        while(<FILE>) {
            next unless /^\s*directory\s+\"*([^";]+)/;
            close FILE;
            return $1;
        }
    }
}

sub getLDAPConfigServer {
    my $data;
    open( FILE, "< /etc/openldap/ldap.conf" ) or die "unable to open /etc/openldap/ldap.conf. $!";
    while(<FILE>) {
        if ( /^host\s+([^\s]*)\s*/i ) { $data = $1; last }
    }
    close(FILE);
    return $data;
}

sub getLDAPConfigBaseDN {
    my $data;
    open( FILE, "< /etc/openldap/ldap.conf" ) or die "unable to open /etc/openldap/ldap.conf. $!";
    while(<FILE>) {
        if ( /^base\s+([^\s]*)/i ) { $data = $1; last }
    }
    close(FILE);
    return $data;
}

sub getLDAPConfigDNSBaseDN {
    my $ret;
    my $mesg = $ldap->search(
                          base => $ldapBaseDN,
                          scope => 'sub',
                          filter => '(objectclass=suseDnsConfiguration)'
                         );
    if( $mesg->code != 0 ) {
        error("LDAP: search failed\n");
        $ldap->unbind;
    } else {
        foreach my $entry ( $mesg->entries ) {
            $ret  = $entry->get_value('suseDefaultBase');
        }
    }
    return $ret;
}

sub statusNamed {
    my $ret = 0;
    if( -f '/var/run/named/named.pid' ) {
        if( open(FILE, "< /var/run/named/named.pid") ) {
            my $pid = <FILE>;
            chomp($pid);
            close(FILE);
            if( open(FILE, "< /proc/$pid/cmdline") ) {
                my $cmd = <FILE>;
                close(FILE);
                $ret = 1 if( $cmd =~ /named/ );
            }
        }
    }
    return $ret;
}

sub parseDynEntries {
    my $file = shift;
    my @ret = ();

    print STDERR "\t\tsearching dyn entries in $file\n" if( $DEBUG );
    open( DUMMY, "< $file" );
    my @data = <DUMMY>;
    close(DUMMY);

    my %entries;
    my $entry = "";
    foreach( my $i=0; $i<@data; $i++ ) {
        if( $data[$i] =~ /^(\S+)\s+(.*)/ ) {
            $entry = $1;
            $entries{$1} .= "$2\n";
        } else {
            $data[$i] =~ /^\s+(.*)/;
            $entries{$entry} .= "\t$1\n";
        }
    }
    foreach my $k ( keys(%entries) ) {
        if( $entries{$k} =~ /TXT\s+\"*[a-f0-9]{34}\"*/i ) {
            print STDERR "\t\t$k identified as dyn entry in zone file $file\n" if( $DEBUG );
            push( @ret, "$k\t$entries{$k}" );
        }
    }
    return @ret;
}

sub sortZones {
    return sort {
        if( $a =~ /in-addr\.arpa/ and $b !~ /in-addr\.arpa/ ) {
            return 1;
        } elsif( $a =~ /in-addr\.arpa/ and $b =~ /in-addr\.arpa/ ) {
            return 0;
        } else {
            return -1;
        }
    } keys(%zones);
}

ACC SHELL 2018