#!/usr/bin/perl
#
# Tool to export netgroups from LDAP to /etc/netgroup format.
#
# TODO:
#  - wrap long groups
#  - handle recursive groups by fetching memberNisNetgroup attributes

use strict;
use warnings;
use Net::LDAP;
use Net::DNS;

use vars qw($ldaphost $base $debug);

$debug = 0;

$ldaphost = $ARGV[0] || find_ldap_server_using_dns() || 'ldap';
$base     = $ARGV[1] || 'ou=Netgroup,dc=skole,dc=skolelinux,dc=no';

my $ldap = new Net::LDAP($ldaphost) || die "Unable to connect to LDAP host '$ldaphost'";

my $mesg = $ldap->bind ; # as anonymous

die("Unable to bind anonymously: $mesg->error") unless ( 0 == $mesg->code );

$mesg = $ldap->search(
                      base   => $base,
                      filter => '(objectClass=nisNetgroup)'
                      );

$mesg->code && die $mesg->error;

foreach my $entry ($mesg->all_entries) {
    my $name    = $entry->get_value("cn");
    my @triples = $entry->get_attribute("nisNetgroupTriple");

    print "$name";
    for my $val (@triples) {
        print " $val";
    }
    print "\n";
    $entry->dump if $debug;
}

$ldap->unbind;   # take down session

# Convert hostname or IP address to the canonical DNS name, to make
# sure DNS CNAMEs etc can be compared.
sub canonize_hostname {
    my $hostname = shift;
    my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($hostname);
    return scalar gethostbyaddr($addrs[0], $addrtype);
}

# Take a list of host names and return the list of pingable hosts
# among these.
sub pingable_hosts {
    return undef unless @_;
    my $servers = join(" ", @_);
    my $pingservers = `/usr/bin/fping -a $servers 2>/dev/null`;
    return split(/\s+/, $pingservers);
}

sub find_ldap_server_using_dns {
    my $res = Net::DNS::Resolver->new;
    $res->dnsrch(1);
    my @nameservers = map { canonize_hostname($_) } $res->nameservers;
    my $srv_query = $res->search("_ldap._tcp", "SRV");
    if (!defined($srv_query)) {
        return undef;
    }
    my @ldapservers;
    my %ldapserveripmap;
    for my $answer ($srv_query->answer) {
        my $servername = canonize_hostname($answer->target);
        push(@ldapservers, $servername);
    }
    # Prefer one of the current DNS servers used if it is in the list
    # of LDAP servers.
    for my $nameserver (pingable_hosts(@nameservers)) {
        for my $ldapserver (@ldapservers) {
            if ($nameserver eq $ldapserver) {
                return $nameserver;
            }
        }
    }
    return (pingable_hosts(@ldapservers))[0];
}
