#!/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:t:", \%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 $type = "netdevices";
if ($opts{t}) {
    $type = $opts{t}
}
my $systembase = "ou=$type,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 userfilter  Filter used to find LDAP user used to update LDAP.
 -t TYPE        Specify type of system(s) to add. TYPE can be: servers,
                workstations, terminals, netdevices or printers.
EOF

    exit $retval;
}

my $retval = 0;
my %hostinfo;

if ($opts{i}) {
    host_handler($opts{i});
} else {
    for_all_hosts(\&host_handler);
}
my $ldap = Net::LDAP->new( $server ) or die "$@";
$ldap->start_tls();

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

print "\nEnter password if you want to activate these changes, and ^c to abort.\n\n";

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;

for my $hostid (keys %hostinfo) {
    my ($fqdn, $ipaddr, $macref, $usedhcp) = @{$hostinfo{$hostid}};
    add_or_update_machine($ldap, $fqdn, $ipaddr, $macref, $usedhcp);
}

$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";
        $hostinfo{$hostid} = [$fqdn, $ipaddr, $macref, 1];
    } elsif ($opts{a}) {
        my $mac = get_primary_ether_hwaddr($macref);
        # Use -I 127.0.0.1 to disable DNS lookup
        $fqdn=`/usr/sbin/update-hostname-from-ip -m -I 127.0.0.1 -M $mac -n`;
        chomp $fqdn;
        print "info: Create GOsa machine for $fqdn [$ipaddr] id $hostid.\n";
        $hostinfo{$hostid} = [$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_gosasystem {
    my ($ldap, $fqdn, $ipaddr, $macref) = @_;

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

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

    my $mesg = $ldap->search(
        base   => $base,
        filter => "(&(|(objectClass=goServer)(objectClass=device)(objectClass=GOhard)(objectClass=gotoWorkstation)(objectClass=gotoTerminal))(|(cn=$hostname)(macAddress=$mac)))",
        );

    print "info: search found " . $mesg->count . " objects\n" if $debug;

    if (0 == $mesg->count) {
        # Create entry of type $type
        my $gotoMode = "active";
        my $gotoSwapServer = "!";
        my $gotoNtpServer = "ntp";
        my $ghCpuType = "-";
        my $ghMemSize = "-";
        my $ghUsbSupport = "-";
        my $ghGfxAdapter = "-";
        my $ghSoundAdapter = "-";
        my $gotoLastUser = "-";
        my $attr;
        if ($type eq 'servers') {
            $attr = [
                'objectClass'  => ['top', 'GOhard', 'goServer'],
                'cn'           => $hostname,
                'macAddress'   => $mac,
                'ipHostNumber' => $ipaddr,
                'gotoMode'     => $gotoMode,
                ];
        } elsif ($type eq 'workstations') {
            $attr = [
                'objectClass'    => ['top', 'gotoWorkstation', 'GOhard'],
                'cn'             => $hostname,
                'macAddress'     => $mac,
                'ipHostNumber'   => $ipaddr,
                'gotoMode'       => $gotoMode,
                'gotoNtpServer'  => $gotoNtpServer,
                'ghCpuType'      => $ghCpuType,
                'ghMemSize'      => $ghMemSize,
                'ghUsbSupport'   => $ghUsbSupport,
                'ghGfxAdapter'   => $ghGfxAdapter,
                'ghSoundAdapter' => $ghSoundAdapter,
                'gotoLastUser'   => $gotoLastUser,
                ];
        } elsif ($type eq 'terminals') {
            $attr = [
                'objectClass'    => ['top', 'gotoTerminal', 'GOhard'],
                'cn'             => $hostname,
                'macAddress'     => $mac,
                'ipHostNumber'   => $ipaddr,
                'gotoMode'       => $gotoMode,
                'gotoNtpServer'  => $gotoNtpServer,
                'ghCpuType'      => $ghCpuType,
                'ghMemSize'      => $ghMemSize,
                'ghUsbSupport'   => $ghUsbSupport,
                'ghGfxAdapter'   => $ghGfxAdapter,
                'ghSoundAdapter' => $ghSoundAdapter,
                'gotoLastUser'   => $gotoLastUser,
                'gotoSwapServer' => $gotoSwapServer,
                ];
        } elsif ($type eq 'printers') {
            my $printerUri = "DUMMY, please change.";
            $attr = [
                'objectClass'    => ['top', 'gotoPrinter'],
                'cn'             => $hostname,
                'macAddress'     => $mac,
                'ipHostNumber'   => $ipaddr,
                'labeledURI'     => $printerUri,
                ];
        } elsif ($type eq 'netdevices') {
            $attr = [
                'objectClass'    => ['top', 'device', 'ipHost', 'ieee802Device'],
                'cn'             => $hostname,
                'macAddress'     => $mac,
                'ipHostNumber'   => $ipaddr,
                ];
        }
        if ($attr) {
            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";
            }
        } else {
            print STDERR "error: Unknown type $type, not adding objects\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 several LDAP objects for the same host (mac=$mac)\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_gosasystem($ldap, $fqdn, $ipaddr, $macref);
    if ($usedhcp) {
        add_or_update_dhcp($ldap, $fqdn, $ipaddr, $macref);
    }
}
