#! /usr/bin/perl

# Copyright (C) 2004  Nick Urbanik <nicku(at)vtc.edu.hk>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

use warnings;
use strict;
use Net::LDAP qw( LDAP_NO_SUCH_OBJECT LDAP_NOT_ALLOWED_ON_NONLEAF );
use Carp;

use constant ADMIN_DN => 'uid=nicku,ou=People,ou=sys,o=ICT';
use constant TOP_DN => 'ou=nicku,o=ICT';
use constant LDAP_SERVER => 'ldap1.tyict.vtc.edu.hk';

sub die_on_error {
    my ( $mesg, $additional_info ) = @_;
    if ( $additional_info ) {
        $additional_info .= ": "
            if index( $additional_info, ':' ) == -1;
    } else {
        $additional_info = '';
    }
    confess $additional_info, "bad message object\n" unless $mesg;
    confess $additional_info, '[', $mesg->code, ']: ', $mesg->error
        if $mesg->code;
}

sub entry_exists($$) {
    my ( $ldap, $entry ) = @_;
    #my $dn = $entry->dn or die "Bad entry $entry";
    my $mesg = $ldap->search(
                             #base => $dn,
                             base => $entry,
                             scope => 'base',
                             filter => '(objectClass=*)',
                             attr => [ 'dn' ],
                             );
    return if $mesg->code == LDAP_NO_SUCH_OBJECT;
    die_on_error $mesg;
    return 1;
}

sub make_ou_entry($$) {
    my ( $ou_name, $parent ) = @_;
    my $entry = new Net::LDAP::Entry;
    my $dn = "ou=$ou_name,$parent";
    $entry->dn( $dn );
    $entry->add(
                objectClass => 'organizationalUnit',
                ou => $ou_name,
                );
    return $entry;
}

# Assume have already bound with permission to write
sub make_ou($$$) {
    my ( $ldap, $ou_name, $parent ) = @_;
    my $entry = make_ou_entry $ou_name, $parent;
    my $mesg = $entry->update( $ldap );
    die_on_error $mesg;
    return 1;
}

# can call with DN or entry.
# assume have bound beforehand.
sub delete_entry($$) {
    my ( $ldap, $dn ) = @_;
    my $mesg = $ldap->delete( $dn );
    if ( $mesg->code == LDAP_NOT_ALLOWED_ON_NONLEAF ) {
        print STDERR "\tEntries exist under \"$dn\"\n",
            "\tso I will not delete this non-leaf node.\n";
        return;
    }
    die_on_error $mesg;
    return 1;
}

use Term::ReadKey;
sub read_password() {
    print "Password: ";
    ReadMode 'noecho';
    my $passwd = ReadLine 0;
    ReadMode 'restore';
    print "\n";
    chomp $passwd;
    return $passwd;
}

sub bind_as_admin($$) {
    my ( $ldap, $binddn ) = @_;
    my $pw = read_password;
    #print "I got $pw\n";
    my $mesg = $ldap->bind( $binddn, 'password' => $pw );
    die "Bad Password\n" if $mesg->code;
    die_on_error $mesg;
}

sub main($) {
    my ( $host ) = @_;
    my $ldap = Net::LDAP->new( $host ) or die "Unable to connect to $host: $!";
    my $mesg = $ldap->start_tls;
    die_on_error $mesg;
    bind_as_admin $ldap, ADMIN_DN;
    foreach my $ou_name ( qw( People Group ) ) {
        my $dn = "ou=$ou_name," . TOP_DN;
        if ( entry_exists $ldap, $dn ) {
            print "deleting $dn\n";
            delete_entry $ldap, $dn;
        } else {
            print "creating $dn\n";
            make_ou $ldap, $ou_name, TOP_DN;
        }
    }
    $ldap->unbind;
}

main LDAP_SERVER;
