#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldapadduser.pl
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# License: GPL
# Date:    20000203
#
# Purpose: Adds a user to LDAP
#
# Copyright (c) 2000 Plugged In Software Pty Ltd.  All rights reserved.

# TODO
# x Check username ( >= 8 chars, all alpha)
# x Check uid number (not negative, less than 65536, etc)
# x Handle ^C's etc gracefully
# x Check automatically generated userid / groupid
# x Check username / uid not already used
#   Get manager's password & bind as manager
#   Display information, ask if ok before adding

# Modules
use strict;
use Net::LDAP;
use Getopt::Std;
use Term::ReadKey;

use vars qw($opt_c $opt_d $opt_g $opt_G $opt_s $opt_u);

# Variables
my($username);
my($homedirectory);
my($gid);
my($gidnumber);
my($gidname);
my($groups);
my($loginshell);
my($uidnumber);
my($uid);
my($cn);
my($gecos);
my($dn);
my($result);
my($gidref);

my($root) = "dc=pisoftware,dc=com";
my($host) = "ldap.staff.plugged.com.au";
my(@objectclass) = ( 'account', 'posixAccount', 'top' );
my($manager) = "cn=Manager,$root";
my $exception = 0;

# Signal handlers
$SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = sub { $exception=1; };

# useradd [-c comment] [-d home_dir]
#              [-e expire_date] [-f inactive_time]
#              [-g initial_group] [-G group[,...]]
#              [-m [-k skeleton_dir] | -M] [-p passwd]
#              [-s shell] [-u uid [ -o]] [-n] [-r] login

# Handle the command line options
getopts('c:d:g:G:s:u:');

if (! $ARGV[0]) {
	print "$0: $0 [-c comment] [-d directory] [-g default group] [-G groups, ..]\n\t[-s shell] [-u uid] username\n";
	exit 1;
}

if ($ARGV[0]) {
	$uid = $ARGV[0];
} else {
	die "Sorry, you must specify a login";
}
if ($opt_c) {
	$cn = $opt_c;
} else {
	$cn = "Plugged In Linux User";
}
$gecos = $cn;
if ($opt_d) {
	$homedirectory = $opt_d;
} else {
	$homedirectory = "/mnt/home/" . $uid;
}
if ($opt_g) {
	$gid = $opt_g;
	$gidref = &findgid($gid);

	$gidnumber = $gidref->[0];
	$gidname = $gidref->[1];

	print "number = $gidnumber, name = $gidname\n";
} else {
	die "Sorry, you must specify a default group for this user";
}
if ($opt_G) {
	$groups = $opt_G;
	print "\$groups = $groups\n";
}
if ($opt_s) {
	$loginshell = $opt_s;
} else {
	$loginshell = "/bin/bash";
}
if ($opt_u) {
	$uidnumber = $opt_u;
} else {
	# find next available uid
	$uidnumber = &finduid;

	print "uid = $uidnumber\n";
}

# Check the uid is valid
&checkuid;

print "Please enter LDAP Managers password: ";

ReadMode 'noecho';
my $password = ReadLine 0;
chomp $password;
ReadMode 'normal';
print "\n";

print "cn $cn\n";
print "uid $uid\n";
print "objectClass @objectclass\n";
print "uidNumber $uidnumber\n";
print "gidNumber $gidnumber\n";
print "loginShell $loginshell\n";
print "homeDirectory $homedirectory\n";
print "gecos $gecos\n";

# Add the user to all the groups in $groups
&addgroups;
#exit 0;

#dn: uid=bmarshal,ou=People,dc=pisoftware,dc=com
#uid: bmarshal
#cn: Brad Marshall
#objectClass: account
#objectClass: posixAccount
#objectClass: top
#userPassword: {crypt}j26bYFB8xQYLE
#loginShell: /bin/bash
#uidNumber: 500
#gidNumber: 120
#homeDirectory: /mnt/home/bmarshal
#gecos: Brad Marshall,,,,

$dn = "cn=".$uid.",ou=People,". $root;
#print "\$dn = $dn\n";

my($ldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";

# Bind to ldap as manager
$ldap->bind(
			dn       => $manager,
			password => $password,
		);

if ($exception) {
	die "Caught a signal";
} else {
	#print "\$dn = $dn\n";
	# Actually add the ldap entry
	$result = $ldap->add (
       dn   => $dn,
       attr => [ 'cn'            => $cn,
				 'uid'           => $uid,
                 'objectClass'   => @objectclass,
				 'uidNumber'     => $uidnumber,
				 'gidNumber'     => $gidnumber,
				 'loginShell'    => $loginshell,
				 'homeDirectory' => $homedirectory,
#				 'gecos'         => $gecos,
			   ]
	);
	$result->code && warn "failed to add entry: ", $result->error;
}

# Subroutines

my(%results);

sub finduid {
	# Find the last uid

	my($entry);
	my(@uids);
	my($lastuid);

	my($ldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";

	$ldap->bind;

	# Search for all accounts
	my($mesg) = $ldap->search( base => $root,
                               filter => '(objectclass=account)'
                             );

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

	# loop thru all the accounts
	foreach $entry ($mesg->entries) {
		# Grab the uid name and number
        my($tmpcn) = $entry->get_value('uid');
        my($tmpnum) = $entry->get_value('uidnumber');
		# .. then stuff it into a hash
        $results{$tmpcn} = $tmpnum;
    }

	# Sort all the uids
	@uids = sort bygroup keys %results;
	# Get the last uid number and add one
	$lastuid = $results{$uids[$#uids]} + 1;

	return $lastuid;
}

sub bygroup {
        $results{$a} <=> $results{$b}
}

sub findgid {
	# Find the gid number and gid name

	my($gid) = shift;
	my($gidname, $gidnumber);
	my($entry);
	my($gidsref);
	my(@gids);

	my($ldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";

	$ldap->bind;

	# Search for all the groups
	my($mesg) = $ldap->search( base => $root,
                               filter => '(objectclass=posixGroup)'
                             );

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

	# Loop thru all the groups
	foreach $entry ($mesg->entries) {
		# If the gid is a number..
		if ($gid =~ /\d{1,3}/) {
			#print "\$gid is a number\n";
			# If the gid is the same as this group's number..
			if ($gid == $entry->get_value('gidnumber')->[0]) {
				# Get the gid number and cn
				$gidnumber = $entry->get_value('gidnumber')->[0];
				$gidname = $entry->get_value('cn')->[0];
				@gids = ($gidnumber, $gidname);
				$gidsref = \@gids;
				# and return it
				return $gidsref;
			}
		} else {
		#	print "\$gid is not a number\n";
			#print $gid, $entry->get('cn'),"\n";
			# if the gid is the same as this group's name
			if ($gid eq $entry->get_value('cn')->[0]) {
				# Get the gid number and cn
				$gidnumber = $entry->get_value('gidnumber')->[0];
				$gidname = $entry->get_value('cn')->[0];
				@gids = ($gidnumber, $gidname);
				$gidsref = \@gids;
				# and return it
				return $gidsref;
			}
		}
	}
	#print "no match\n";
}

sub addgroups {
	my(@group);
	my($gidsref);
	my($groupdn);
	my($grouplist);

	if ($groups) {
		$grouplist = $groups . " " . $gidname;
	} else {
		$grouplist = $gidname;
	}

	if ($grouplist) {
		my($groupldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";

		print "\$manager = $manager, \$password = $password\n";

		$groupldap->bind(
			dn       => $manager,
			password => $password,
		);

		print "groups = $grouplist\n";
		@group = split(/ /, $grouplist);
		my($group);
		foreach $group (@group) {
			$gidsref = &findgid($group);
			#print $gidsref->[1],"\n";
			my($tmp) = $gidsref->[1];
			$groupdn = "cn=$tmp,ou=Group,$root";
			print "\$groupdn = $groupdn, \$uid = $uid\n";
			$groupldap->modify( $groupdn, add => { memberuid => $uid } );
		}
	}
}

sub checkuid {
	if ($uid !~ /[a-z]{3,8}/) {
		die "Sorry, username must consist solely of letters and be between 3 and 8 characters.";
	}
	if (($uidnumber > 65535) || ($uidnumber < 0)) {
		die "Sorry, uid number must be less than 65535 and greater than 0";
	}

	my($ldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";

	$ldap->bind;

	my($mesg) = $ldap->search( base => $root,
                               filter => '(objectclass=account)'
                             );

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

	my($entry);
	foreach $entry ($mesg->entries) {
		my($tmpcn) = $entry->get_value('uid')->[0];
		my($tmpuidnumber) = $entry->get_value('uidnumber')->[0];
		if ($tmpcn eq $uid) {
			die "Sorry, username $uid already exists";
		}
		if ($tmpuidnumber == $uidnumber) {
			die "Sorry, userid $uidnumber already exists";
		}
	}
}
