#!/usr/bin/perl -W
use strict;
# 
# Author: Harald Inge Boerseth <haraldib@broadpark.no>
# 
# This script is written to enter users into the
# LDAP DB from users defined in a LDIF file. 
# In this case the LDIF file is generated from a Novel 6.0 
# authentication server. An example of what an LDIF entry looks 
# like in this case can be seen at the end of the script. 
# It should be a simple task to adapt the script to other LDIF files.
#
# Note that the script corrects some sequences of UTF8 encoding
# that were found to be wrong as exported from the Novell 6.0 server.
# These lines of code should be removed when reading LDIF entries
# that are believed to be correct regarding UTF-8 encoding.
# The corrections are done in the subroutine utf8_corr().
#
# Note that some data definitions have to be entered before
# running the script. Look for comments starting with 'Replace ...'.
# Good luck!!!

use Net::LDAP;
use Net::LDAP::LDIF;
use File::Find;
use File::Copy;
use File::Glob;

# Replace homebase as appropriate...
my $homebase   = '/skole/tjener/home0/';
# Replace mailbase as appropriate...
my $mailbase   = '/var/lib/maildirs/';
# Replace nn below...
my $rootdn     = 'cn=<nn>,ou=people,dc=skole,dc=skolelinux,dc=no';
# Replace root password blow...
my $rootpasswd = '<password>';
my $basedn     = 'dc=skole,dc=skolelinux,dc=no';
my $cntr       = 1;

# Replace first uid value and gid value below...
# ...the script assumes that uid's and gid's following
# this value is not already taken, i.e. the lowest free number
# for uid and gid should be used.
my $uid_val    = 10199;
my $gid_val    = 10199;

my $ldap       = Net::LDAP->new('localhost', onerror => 'warn');
#  Replace name of LDIF-file below...
my $ldif       = Net::LDAP::LDIF->new("<ldif file>", "r", onerror => 'warn');

$ldap->bind($rootdn, password => $rootpasswd);

while ( not $ldif->eof() ) {
    my $ldif_entry = $ldif->read_entry();
    if ( $ldif->error() ) {
 	print "Error msg: ",$ldif->error(),"\n";
	print "Error lines:\n",$ldif->error_lines(),"\n";

    } else {
	my $dn = $ldif_entry->dn;

	my $uid = undef;
	$dn =~ /cn=(\w+)/ && ($uid = $1);

	# Skip entires where the uid is not provided...
	next unless defined $uid;

	$ldif_entry->replace('sn' => 'NN') 
	  unless $ldif_entry->exists('sn');
	$ldif_entry->replace('givenName' => 'NN') 
	  unless $ldif_entry->exists('givenName');
	my $given_name = $ldif_entry->get_value('givenName');
	my $sn = $ldif_entry->get_value('sn');

	# The attributes givenName and sn contain wrong
	# UTF8 encodings for the characters æ,ø,a,Æ,Ø,Å...
	# ...this is a special operation needed for
	# this specific input file only.
	utf8_corr($given_name);
	utf8_corr($sn);

	my $homedir = $homebase . $uid;
	my $maildir = "/var/lib/maildirs/" . $uid;
	my $new_entry = Net::LDAP::Entry->new;

	$uid_val++;
	$gid_val++;

	$new_entry->dn("uid=$uid,ou=people,dc=skole,dc=skolelinux,dc=no");
	$new_entry->
	    add('objectClass'      => ['posixAccount','imapUser'],
		'cn'               => $given_name . " " . $sn,
		'uid'              => $uid,
		'uidNumber'        => $uid_val,
		'gidNumber'        => $gid_val,
		'homeDirectory'    => $homedir,
		'mailMessageStore' => $maildir,
		'loginShell'       => '/bin/bash',
		'userPassword'     => $uid);

	print "Processing: ", $new_entry->dn, "\n";
	$ldap->add($new_entry);
	print "addSuccess\n";
	create_dir($new_entry);
	create_grp($new_entry);
	print "\n"; 
    }
}
$ldap->unbind();
$ldif->done();
exit;


sub utf8_corr {
    # The LDIF file geneated from Novel contained errors
    # regarding UTF8 encoding of æ,ø,å,Æ,Ø,Å...
    $_[0] =~ s/\303\217/\303\230/g;
    $_[0] =~ s/\302\265/\303\246/g;
    $_[0] =~ s/\302\260/\303\270/g;
    $_[0] =~ s/\303\225/\303\245/g;
}


sub create_dir {
    my $entry        = shift;
    my $uidnumber    = $entry->get_value('uidnumber');
    my $gidnumber    = $entry->get_value('gidnumber');
    my $homedir      = $entry->get_value('homedirectory');
    my $cn           = $entry->get_value('cn');

    if (-d $homedir) {
	print "homedirOK (already created) $homedir\n";
	return;
    }
    umask 0022;
    mkdir $homedir, 0755 || die "Unable to create $homedir: $!\n";
    my @files = </etc/skel/.* /etc/skel/*>;
    foreach (@files) {
	next if m(^/etc/skel/\.\.$);
	next if m(^/etc/skel/\.$);
	`cp -r $_ $homedir`;
    }
    find(sub {chown($uidnumber, $gidnumber, $_)}, $homedir);
    print "homedirOK, $cn ( $homedir )\n";
}


sub create_grp {
    my $entry     = shift;
    my $gidnumber = $entry->get_value('gidnumber');
    my $cn        = $entry->get_value('cn');
    my $uid       = $entry->get_value('uid');
    my $mesg      = $ldap->search (base   => "ou=group,$basedn",
				   filter => "gidNumber=$gidnumber");
    if (! $mesg->count()){
	# Group id unknown...
	my $new_entry = Net::LDAP::Entry->new();
	$new_entry->dn("cn=$uid,ou=group,$basedn");
	$new_entry->add(objectclass => 'posixGroup',
			cn          => $uid,
			gidNumber   => $gidnumber);
	$ldap->add($new_entry);
	print "groupOK, $cn ( $gidnumber )\n";
    } else {
	print "groupOK (already created), $cn ( $gidnumber )\n";
    }
}

# Example of LDIF entry processed by the script
# Some values (<...>) changed for security reasons...
#dn: cn=<cn>,ou=elev,ou=Users,o=UVNETT
#changetype: add
#givenName: <givenname>
#fullName:: <QXlFYSBLZWxow5VziFJ5x2g=>
#Language: ENGLISH
#sn: <sn>
#securityEquals: cn=Elever,ou=Users,o=UVNETT
#passwordUniqueRequired: TRUE
#passwordRequired: TRUE
#passwordMinimumLength: 5
#passwordExpirationTime: 20030429113427Z
#passwordExpirationInterval: 7776000
#objectClass: inetOrgPerson
#objectClass: organizationalPerson
#objectClass: person
#objectClass: ndsLoginProperties
#objectClass: top
#loginTime: 20030218122529Z
#loginGraceLimit: 6
#ndsHomeDirectory: cn=EMBLA_DATA,ou=Drift,o=UVNETT#0#<...>
#groupMembership: cn=Elever,ou=Users,o=UVNETT
#cn: <cn>
#ACL: 2#subtree#cn=<cn>,ou=elev,ou=Users,o=UVNETT#[All Attributes Rights]
#ACL: 6#entry#cn=<cn>,ou=elev,ou=Users,o=UVNETT#loginScript
#ACL: 2#entry#[Public]#messageServer
#ACL: 2#entry#[Root]#groupMembership
#ACL: 6#entry#cn=<cn>,ou=elev,ou=Users,o=UVNETT#printJobConfiguration
#ACL: 2#entry#[Root]#networkAddress
