#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Std;
use Socket;
use Net::LDAP; # From libnet-ldap-perl debian package
use SiteSummary;
use Debian::Edu qw(prompt4password find_ldap_server find_ldap_base);

my $debug = 0;

my %opts;

getopts("adi:u:", \%opts) || usage(1);

$debug = 1 if $opts{d};

my $server = $ARGV[0] || find_ldap_server() || "ldap";
my $base   = $ARGV[1] || find_ldap_base($server)
    || "dc=skole,dc=skolelinux,dc=no";

my $serverbase = "ou=servers,ou=systems,$base";
my $dhcpbase = "cn=dhcp,cn=tjener,ou=servers,ou=systems,$base";

my $userfilter = $opts{u} || "(cn=admin)";

sub usage {
    my $retval = shift;
    print <<EOF;
sitesummary2ldapdhcp [FQDN to ldap server] [LDAP base to use]
Update or add machine objects to LDAP based on sitesummary
information.

 -a             Add new hosts as well as update existing hosts.
 -d             Enable debug output.
 -i ID          Handle host with the given sitesummary ID only.
 -u userfiler   Filter used to find LDAP user used to update LDAP.

EOF
    exit $retval;
}

my $retval = 0;

my $ldap = Net::LDAP->new( $server ) or die "$@";
$ldap->start_tls();

my $binddn = (find_ldap_objects($ldap, $userfilter))[0]->dn();

print "Connecting to LDAP as $binddn\n";
my $bindpw = prompt4password('enter password: ', -echo => '*');

my $mesg = $ldap->bind($binddn, password => $bindpw);
die "Unable to bind to LDAP server: " . $mesg->error if $mesg->code;

if ($opts{i}) {
    host_handler($opts{i});
} else {
    for_all_hosts(\&host_handler);
}
$mesg = $ldap->unbind;
exit $retval;


sub host_handler {
    my $hostid = shift;
    my $ipaddr = SiteSummary::get_primary_ip_address($hostid);
    my $fqdn = scalar gethostbyaddr(inet_aton($ipaddr), AF_INET);
    my $macref = [(get_ether_hwaddr($hostid))];

    # Update MAC and IP if name has been changed
    if ($fqdn && $fqdn !~ m/^auto-mac-/) {
        print "info: Updating machine $fqdn [$ipaddr] id $hostid.\n";
        add_or_update_machine($ldap, $fqdn, $ipaddr, $macref, 1);
    } elsif ($opts{a}) {
        my $mac = get_primary_ether_hwaddr($macref);
        $mac =~ s/[^0-9a-f-]/-/gi;
        $fqdn = "auto-mac-$mac";
        print "info: Create GOsa machine for $fqdn [$ipaddr] id $hostid.\n";
        add_or_update_machine($ldap, $fqdn, $ipaddr, $macref, 0);
    } else {
        print "info: Ignoring new machine [$ipaddr] id $hostid.\n";
    }
}

sub get_ether_hwaddr {
    my $hostid = shift;
    my $path = get_filepath_current($hostid, "/system/ifconfig-a");
    if (open(FILE, "<", $path)) {
        my $sysinfo = 0;
        my @hwaddr = ();
        while (<FILE>) {
            chomp;
            if (m/Link encap:Ethernet  HWaddr (.+\S)\s*$/) {
                push(@hwaddr, $1);
            }
        }
        close(FILE);
        return @hwaddr;
    } else {
        return undef;
    }
}
sub get_primary_ether_hwaddr {
    # FIXME How to handle several MAC addresses?
    my $macref = shift;
    my $mac = (@{$macref})[0];
}

sub find_ldap_objects {
    my ($ldap, $filter) = @_;
    my $mesg = $ldap->search(
        base   => $base,
        filter => $filter,
        );
    return $mesg->all_entries;
}

sub add_or_update_gosaserver {
    my ($ldap, $fqdn, $ipaddr, $macref) = @_;

    my $mac = get_primary_ether_hwaddr($macref);
    my ($hostname, $dnsdomain) = split(/\./, $fqdn, 2);

    my $cn = "cn=$hostname,$serverbase";

    my $mesg = $ldap->search(
        base   => $base,
        filter => "(&(objectClass=goServer)(cn=$hostname))",
        );

    print "info: goServer search found " . $mesg->count . " objects\n" if $debug;
    if (0 == $mesg->count) {
        # Create
        my $attr = [
            'objectClass'  => ['top', 'GOhard', 'goServer'],
            'cn'           => $hostname,
            'macAddress'   => $mac,
            'ipHostNumber' => $ipaddr,
            'gotoMode'     => 'locked',
            ];

        my $result = $ldap->add($cn, attr => $attr);
        if ($result->code) {
            my $err = $result->error;
            print STDERR "error: Unable to add LDAP object $cn - $err\n";
        }
    } elsif (1 == $mesg->count) {
        # Update

        foreach my $entry ($mesg->all_entries) {
            $entry->dump if $debug;
            $entry->replace(
                'macAddress'   => $mac,
                'ipHostNumber' => $ipaddr,
                );
            my $result = $entry->update($ldap);
            if ($result->code) {
                my $err = $result->error;
                print STDERR "error: Unable to update LDAP object $cn - $err\n";
            }
        }
    } else {
        print STDERR "error: Not sure how to handle this\n";
    }
}

sub add_or_update_dhcp {
    my ($ldap, $fqdn, $ipaddr, $macref) = @_;

    my $mac = get_primary_ether_hwaddr($macref);
    my ($hostname, $dnsdomain) = split(/\./, $fqdn, 2);

    my $cn = "cn=$hostname,$dhcpbase";

    my $mesg = $ldap->search(
        base   => $base,
        filter => "(&(objectClass=dhcpHost)(cn=$hostname))"
        );

    print "info: dhcpHost search found " . $mesg->count . " objects\n" if $debug;
    if (0 == $mesg->count) {
        # Create
        my $attr = [
            'objectClass'    => 'dhcpHost',
            'cn'             => $hostname,
            'dhcpHWAddress'  => "ethernet $mac",
            'dhcpStatements' => "fixed-address $fqdn"
            ];

        my $result = $ldap->add($cn, attr => $attr);
        if ($result->code) {
            my $err = $result->error;
            print STDERR "error: Unable to add DHCP LDAP object $cn - $err\n";
        }
    } elsif (1 == $mesg->count) {
        # Update

        foreach my $entry ($mesg->all_entries) {
            $entry->dump if $debug;
            $entry->replace(
                'dhcpHWAddress'  => "ethernet $mac",
                'dhcpStatements' => "fixed-address $fqdn"
                );
            my $result = $entry->update($ldap);
            if ($result->code) {
                my $err = $result->error;
                print STDERR "error: Unable to update DHCP LDAP object $cn - $err\n";
        }
        }
    } else {
        print STDERR "error: Not sure how to handle this\n";
    }
}

sub add_or_update_machine {
    my ($ldap, $fqdn, $ipaddr, $macref, $usedhcp) = @_;
    print "Updating host entry for $fqdn\n" if $debug;
    add_or_update_gosaserver($ldap, $fqdn, $ipaddr, $macref);
    if ($usedhcp) {
        add_or_update_dhcp($ldap, $fqdn, $ipaddr, $macref);
    }
}
