Introduction to LDAP

Brad Marshall
brad.marshall@member.sage-au.org.au
http://quark.humbug.org.au/

This is very old - more up to date LDAP related tutes and papers are available.

Index


What is LDAP

Acroynms

LDAP
Lightweight Directory Access Protocol
DN
Distinguish Name
RDN
Relative Distinuished Name
DIT
Directory Information Tree
LDIF
LDAP Data Interchange Format
OID
Object Identifier

LDIF

Schema

Attribute abbreviations

See RFC2256
uid
User id
cn
Common Name
sn
Surname
l
Location
ou
Organisational Unit
o
Organisation
dc
Domain Component
st
State
c
Country

Search Filters

LDAP URL

Definition taken from RFC1959

<ldapurl> ::= "ldap://" [ <hostport> ] "/" <dn> [ "?" <attributes>
                        [ "?" <scope> "?" <filter> ] ]
<hostport> ::= <hostname> [ ":" <portnumber> ]
<dn> ::= a string as defined in RFC 1485
<attributes> ::= NULL | <attributelist>
<attributelist> ::= <attributetype>
                  | <attributetype> [ "," <attributelist> ]
<attributetype> ::= a string as defined in RFC 1777
<scope> ::= "base" | "one" | "sub"
<filter> ::= a string as defined in RFC 1558

Explanations:

DN
Distinguished name
Attribute list
List of attributes you want returned
Scope
base = base object search
one = one level search
sub = subtree search
Filter
Standard LDAP search filter

Examples:

LDAP command line tools

ldapadd, ldapmodify
Used to add or modify ldap entries
$ ldapmodify -r -D 'cn=foo,dc=bar,dc=com' -W < /tmp/user.ldif
ldapdelete
Used to delete entries
$ ldapdelete -D 'cn=foo,dc=bar,dc=com' -W 'cn=user,dc=bar,dc=com'
ldapsearch
Used to search ldap servers
$ ldapsearch -L -D 'cn=foo,dc=bar,dc=com' 'objectclass=posixAccount'

Installing and Configuring LDAP

Servers

Openldap

LDAP Server architecture

Replication

Replication Options

a. All modifications go to the master LDAP server

[ Replica A ]

b. Using referrals

[ Replica B ]
  1. Client sends modification to replica
  2. Replica returns referral to master
  3. Client resubmits modification to master
  4. Master returns results to client
  5. Master updates replica with change

c. Using chaining

[ Replica C ]
  1. Client sends modification to replica
  2. Replica forwards request to master
  3. Master returns result to replica
  4. Replica forwards result to client
  5. Master updates replica

Example slapd.conf


#
# See slapd.conf(5) for details on configuration options.
# This file should NOT be world readable.
#
include         /etc/openldap/slapd.at.conf
include         /etc/openldap/slapd.oc.conf
schemacheck     off

pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

defaultaccess read

access to attr=userpassword
   by self write
   by * read

access to *
   by self write
   by dn=".+" read
   by * read
#######################################################################
# ldbm database definitions
#######################################################################

database        ldbm
suffix          "dc=pisoftware, dc=com"
rootdn          "cn=Manager, dc=pisoftware, dc=com"
rootpw          {crypt}lAn4J@KmNp9
replica host=cox.staff.plugged.com.au:389
    binddn="cn=Manager,dc=pisoftware,dc=com"
    bindmethod=simple credentials=secret
    replogfile /var/lib/openldap/replication.log
# cleartext passwords, especially for the rootdn, should
# be avoid.  See slapd.conf(5) for details.
directory       /var/lib/openldap/

slapd.conf ACLs

LDAP Applications

Application Architecture

[ Applications Architecture ]

Using Multiple Applications

[ LDAP Applications ]

System Authentication

Uses RFC2307

Migration

Used PADLs MigrationTools
Script Migrates
migrate_fstab.pl /etc/fstab
migrate_group.pl /etc/group
migrate_hosts.pl /etc/hosts
migrate_networks.pl /etc/networks
migrate_passwd.pl /etc/passwd
migrate_protocols.pl /etc/protocols
migrate_rpc.pl /etc/rpc
migrate_services.pl /etc/services

These scripts are called on the appropriate file in /etc in the following manner:

     # ./migrate_passwd.pl /etc/passwd ./passwd.ldif

The migration tools also provide scripts to automatically migrate all configuration to LDAP, using migrate_all_{online,offline}.sh. See the README distributed with the package for more details.

Example LDIF


dn: uid=bmarshal,ou=People,dc=pisoftware,dc=com
uid: bmarshal
cn: Brad Marshall
objectclass: account
objectclass: posixAccount
objectclass: top
loginshell: /bin/bash
uidnumber: 500
gidnumber: 120
homedirectory: /mnt/home/bmarshal
gecos: Brad Marshall,,,,
userpassword: {crypt}aknbKIfeaxs

dn: cn=sysadmin,ou=Group,dc=pisoftware,dc=com
objectclass: posixGroup
objectclass: top
cn: sysadmin
gidnumber: 160
memberuid: bmarshal
memberuid: dwood
memberuid: jparker

Installation

Install from PADL

/etc/ldap.conf


BASE        dc=foo,dc=com
HOST        ldap.server.com
pam_crypt   local

/etc/nsswitch.conf

Add ldap to the passwd, shadow and group entries in /etc/nsswitch.conf. Be aware of the effects of putting it first or last.

/etc/pam.d

Need similar for every app you want to use ldap

/etc/pam.d/ssh

From RedHat 6.2

#%PAM-1.0
auth       sufficient   /lib/security/pam_ldap.so
auth       required     /lib/security/pam_pwdb.so shadow nullok try_first_pass
auth       required     /lib/security/pam_nologin.so
account    sufficient   /lib/security/pam_ldap.so
account    required     /lib/security/pam_pwdb.so
password   required     /lib/security/pam_cracklib.so
password   sufficient   /lib/security/pam_ldap.so
password   required     /lib/security/pam_pwdb.so shadow nullok use_authtok
session    sufficient   /lib/security/pam_ldap.so
session    required     /lib/security/pam_pwdb.so

Apache user auth

Squid ACLs

Netscape Address book

To add a LDAP server to the Netscape Address book:

[ Netscape Address Book ]

Netscape Address Book

[ Netscape New Directory ]

Add a new directory to the address book

[ Netscape Setting up Directory ]

Add the new directory into the addressbook search

[ Netscape Searching Directory ]

Example of searching directory server

The email address returned is the contents of the `mail' attribute.

Mutt Address book


#!/usr/bin/perl -w
# by Ben Collins , butchered by Marco d'Itri 
# Hacked by Brad Marshall  for use at PI
# to use, add to ~/.muttrc:
#  set query_command="/mnt/home/linux/bin/pi-ldap-query %s"

use strict;

my @attrs = qw(sn cn uid);
my $base = 'ou=People, dc=pisoftware, dc=com';
my $server = 'morris';
my $port = 389;

die "Usage: $0  [...]\n" if not $ARGV[0];

eval 'require Net::LDAP;';
die "Could not load Net::LDAP: $@\n" if $@;

my $ldap = Net::LDAP->new($server, port => $port) or
	die "Could not contact LDAP server $server:$port";
$ldap->bind or die 'Could not bind';

my @results = ();

foreach my $search (@ARGV) {
	my $query = join '', map { "($_=*$search*)" } @attrs;
    my $mesg = $ldap->search(base => $base, filter => "(|$query)")
		or die 'Failed search';
    foreach my $entry ($mesg->entries) {
		my $uid = $entry->get('uid');
		next unless (defined $uid);
		my $fname = $entry->get('cn');
		#my $lname = $entry->get('sn');
		my $mail = $entry->get('mail');
		push @results,
			"<$$mail[0]>\t$$fname[0]\tPI\n";
    }
}

$ldap->unbind;

print 'LDAP query: found ', scalar @results, "\n", @results;
exit 1 unless @results;

Sendmail routing

http://sendmail.net/?feed=ldaprouting http://sendmail.net/?feed=rfc1777

Samba

http://www.unav.es/cti/ldap-smb-howto.html

Netscape Roaming access

http://www.linuxworld.com/linuxworld/lw-1999-09/lw-09-ldap-netscape.html

Programming LDAP

Perl

Basic Query


#!/usr/bin/perl -w

use strict;
use Net::LDAP;

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

$ldap->bind;

my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
					   filter => '(objectclass=*)');

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

my($entry);

map { $_->dump } $mesg->all_entries;
# OR
foreach $entry ($mesg->all_entries) { $entry->dump; }

$ldap->unbind;

Adding Entries


#!/usr/bin/perl -w

use strict;
use Net::LDAP;

my $root = "dc=pisoftware,dc=com";
my $manager = "cn=Manager,$root";
my $password = 'secret';

my $groupdn = "cn=test,ou=Group,$root";
my $uid = "test";

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

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

#$ldap->modify( $groupdn, add => { memberuid => $uid } );
$result = $ldap->add( dn => $groupdn, 
			attr => [ 'cn' => 'Test User',
						'sn' => 'User',
						'uid' => 'test',
					];

$result->code && warn "failed to add entry: ", $result->error;

$ldap->unbind;

Deleting Entries


#!/usr/bin/perl -w

use strict;
use Net::LDAP;

my $root = "dc=pisoftware,dc=com";
my $manager = "cn=Manager,$root";
my $password = 'secret';

my $groupdn = "cn=test,ou=Group,$root";
my $uid = "test";

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

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

$ldap->delete( $groupdn );
$ldap->unbind;

Modifying Entries


$ldap->modify( $dn,
	changes => [
		add     => [ sn => 'User' ],                    # Add sn=User
		delete  => [ faxNumber => []],                  # Delete all fax numbers
		delete  => [ telephoneNumber => ['911']],       # delete phone number 911
		replace => [ email => 'test@pisoftware.com']    # change email address
	]
);

Real World Examples

Rsync Backup Scripts

Given an ldap server with entries for each machine, does a rsync backup from them using the module names given in the rsync attribute. This allows automatic backing up of hosts on whatever schedule the script is run from cron.

Schema


objectclass machine
        requires
                                objectClass,
                                hostname,
                                cpu,
                                ram,
                                usage
        allows
                                rsync

LDIF Extract


dn: cn=carmack,ou=Machines,dc=pisoftware,dc=com
hostname: carmack
objectclass: top
objectclass: machine
usage: workstation
rsync: etc
cpu: PIII 550
ram: 256M

Code


#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldaprsync.pl
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# Date:    20000801
#
# Purpose: Rsync certain modules from hosts in ldap
#
# Copyright (c) 2000 Plugged In Software Pty Ltd.  All rights reserved.

use strict;
use Net::LDAP;

my %config = ( "destdir" => "/opt/hosts",
				"rsyncoptions" => "--compress --archive --one-file-system");

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

$ldap->bind;

my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
					   filter => '(objectclass=machine)');

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

my($entry,$attr);

my(%results);
foreach $entry ($mesg->entries) {
	#print "DN = ",$entry->dn,"\n";
	#my($tmpcn) = $entry->get('uid');
	my(@rsync) = $entry->get('rsync');
	my(@hostname) = $entry->get('hostname');
	my($host) = join(" ", @hostname);
	foreach my $src (@rsync) {
		print "Getting $host $src\n";

		my $direct;
		if ($src =~ /^(.*)\//g) {
			$direct = $1;
		} else {
			$direct = $src;
		}
		my $dir = "$config{destdir}/$host/$direct";
		if ( ! -d $dir ) {
			system("mkdir -p $dir");
		}
		my @ary = split /\s+/, "rsync $config{rsyncoptions} $host\::$src $dir";
		system(@ary) == 0 or warn "system @ary failed: $?\n";
		#print "@ary\n" or warn "system @ary failed: $?\n";
	}
#	foreach $attr ($entry->attributes) {
#		print $attr,": ", join(" ", $entry->get($attr)), "\n";
#	}
#	print "\n";
}

$ldap->unbind;

Squid Authentication

Dumps the username and password pairs from LDAP, optionally using a httppassword attribute instead of the userpassword attribute, if it exists. Useful for doing proxy authentication if you don't want to, or can't, use existing ldap authentication methods.


#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldapdumpsquid.pl (based on ldapdumpusers.pl)
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# Date:    20000717
#
# Purpose: Dumps the users into a htaccess type file
#          Useful for apache and squid.
#
# Copyright (c) 2000 Plugged In Software Pty Ltd.  All rights reserved.

use strict;
use Net::LDAP;

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

$ldap->bind;

my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
					   filter => '(objectclass=account)');

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

my($entry,$attr);

my(%results);
foreach $entry ($mesg->entries) {
# postgres:!!:113:113:PostgreSQL Server:/var/lib/pgsql:/bin/sh
# uid:userpassword:uidnumber:gidnumber:gecos:homedirectory:loginshell

	my($tmpcn) = $entry->get('uid');
	my($tmpnum) = $entry->get('uidnumber');
	my($tmpgid) = $entry->get('gidnumber');
	my($tmppassword) = $entry->get('httppassword');
	#defined $tmppassword and do { print ref $tmppassword,"\n"; print $tmppassword,"\n" };
	defined $tmppassword or ($tmppassword) = $entry->get('userpassword');
	if ($tmppassword) {
		$tmppassword =~ s/^{crypt}//;
	}
	#my($tmpgecos) = $entry->get('gecos') || [ "Plugged In User" ];
	my($tmpgecos) = $entry->get('cn');
	$tmpgecos ||= "";
	my($tmphome) = $entry->get('homedirectory');
	my($tmpshell) = $entry->get('loginshell');
	$tmpshell ||= "";

	if (! $tmppassword) {
		$tmppassword = "x";
	}
	if ($tmppassword eq "x") {
		next;
	}
	if ($tmppassword =~ /^\*/) {
		next;
	}

	if ($tmpcn =~ /(wallppp)|(order)|(helpdesk)|(javadoc)|(postgres)|(nocol)|(backup)|(root)|(pppuser)/) {
		next;
	}
	my($tmpstr) = "$tmppassword";
	#print "$tmpcn\n";
	#if ($tmpnum => 500) {
	#	$results{$tmpcn} = $tmpstr;
	#}
	$results{$tmpcn} = $tmpstr;
}

my($foo);
foreach $foo (sort keys %results) {
	#printf "%3d\t%s\n",$results{$foo}, $foo;
	print "$foo:$results{$foo}\n";
}

$ldap->unbind;

User Admin Scripts

ldapadduser.pl


#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldapadduser.pl
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# 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('uid');
        my($tmpnum) = $entry->get('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('gidnumber')->[0]) {
				# Get the gid number and cn
				$gidnumber = $entry->get('gidnumber')->[0];
				$gidname = $entry->get('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('cn')->[0]) {
				# Get the gid number and cn
				$gidnumber = $entry->get('gidnumber')->[0];
				$gidname = $entry->get('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('uid')->[0];
		my($tmpuidnumber) = $entry->get('uidnumber')->[0];
		if ($tmpcn eq $uid) {
			die "Sorry, username $uid already exists";
		}
		if ($tmpuidnumber == $uidnumber) {
			die "Sorry, userid $uidnumber already exists";
		}
	}
}

ldapdeluser.pl


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

# TODO
#   Remove the user from all groups they're in

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

use vars qw($opt_g);

my($uid);
my($gidnumber);
my($gidref);
my($cn);
my($dn);
my($result);
my($entry);

my($root) = "dc=pisoftware,dc=com";
my($host) = "ldap.staff.plugged.com.au";
my $exception = 0;

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

# groupadd [-g gid [-o]] [-r] [-f] group

if (! $ARGV[0]) {
    print "$0: $0 username\n";
    exit 1;
}

if ($ARGV[0]) {
	$uid = $ARGV[0];
	if ($uid !~ /[a-z]{2,8}/) {
		die "Sorry, username must consist solely of letters and be between 3 and 8 characters.";
	}
} else {
	die "Sorry, you must specify a username";
}

$dn = &finduid;

#dn: cn=support,ou=Group,dc=pisoftware,dc=com
#objectclass: posixGroup
#objectclass: top
#cn: support
#gidnumber: 140

my($manager) = "cn=Manager,$root";
print "$manager\n";

print "Please enter LDAP Managers password: ";

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

print "\$dn = $dn\n";

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

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

if ($exception) {
	die "Caught a signal";
} else {
	$result = $ldap->delete ( $dn );
	&removegroup;
	$result->code && warn "failed to delete entry: ", $result->error ;
};


# Subroutines

sub finduid {
	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;

	foreach $entry ($mesg->entries) {
		my($tmpuid) = $entry->get('uid')->[0];
		#print "\$tmpuid = $tmpuid\n";
		#print "$uid $tmpuid\n";
		if ($uid eq $tmpuid) {
			#print "Found it - \$tmpcn = $tmpcn\n";
			my($tmpdn) = $entry->dn;
			return $tmpdn;
		}
	}
	die "Sorry, can't find the user you want to delete.";
}

sub removegroup {
	my(@groups);
	my($group);

	my($ldap) = Net::LDAP->new($host) or die "Can't bind to ldap: $!\n";
	
	$ldap->bind;
	
	my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
	                       filter => '(objectclass=posixGroup)');
	
	$mesg->code && die $mesg->error;
	
	foreach $entry ($mesg->entries) {
	    my($tmpcn) = $entry->get('cn');
	    my($tmpnum) = $entry->get('gidnumber');
	    my(@members) = $entry->get('memberuid');
	
	    #print "\@members = @members\n";
	    #print "Checking $tmpcn...";
	
	    my %t = ();
	    @t{@members} = 1;
	    if (exists $t{$uid}) {
	        #print "yes.";
	        # Push the cn onto an array
	        push @groups, $tmpcn;
	    }
	    #print "\n";
	}

	$ldap->unbind;

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

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

	foreach $group (@groups) {
		my($tmpdn) = "cn=$group,ou=Group,$root";
		$authldap->modify( $tmpdn, delete => { memberuid => $uid } );
	}

	$authldap->unbind;
}

ldapdumpusers.pl


#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldapdumpusers.pl
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# Date:    20000203
#
# Purpose: Dumps the users into an /etc/passwd type file
#
# Copyright (c) 2000 Plugged In Software Pty Ltd.  All rights reserved.

use strict;
use Net::LDAP;

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

$ldap->bind;

my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
					   filter => '(objectclass=account)');

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

my($entry,$attr);

my(%results);
foreach $entry ($mesg->entries) {
	#print "DN = ",$entry->dn,"\n";
	#foreach $attr ($entry->attributes) {
	#	print $attr,": ", join(" ", $entry->get($attr)), "\n";
	#}
#	print $entry->get('cn'), " ", $entry->get('gidnumber'), "\n";
	#printf "%3d\t%s\n",$entry->get('gidnumber'), $entry->get('cn');

# postgres:!!:113:113:PostgreSQL Server:/var/lib/pgsql:/bin/sh
# uid:userpassword:uidnumber:gidnumber:gecos:homedirectory:loginshell

	my($tmpcn) = $entry->get('uid');
	my($tmpnum) = $entry->get('uidnumber');
	my($tmpgid) = $entry->get('gidnumber');
	my($tmppassword) = $entry->get('userpassword');
	if ($tmppassword) {
		$tmppassword =~ s/^{crypt}//;
	}
	#my($tmpgecos) = $entry->get('gecos') || [ "Plugged In User" ];
	my($tmpgecos) = $entry->get('cn');
	$tmpgecos ||= "";
	my($tmphome) = $entry->get('homedirectory');
	my($tmpshell) = $entry->get('loginshell');
	$tmpshell ||= "";

	#if ($tmpcn eq "root") {
	#	print "Root passwd = $tmppassword\n";
	#}
	if (! $tmppassword) {
		$tmppassword = "x";
	}
	my($tmpstr) = "$tmppassword:$tmpnum:$tmpgid:$tmpgecos:$tmphome:$tmpshell";
	#print "$tmpcn\n";
	$results{$tmpcn} = $tmpstr;
}

sub bygroup {
	(split /:/,$results{$a})[1] <=> (split /:/,$results{$b})[1]
}

my($foo);
foreach $foo (sort bygroup keys %results) {
	#printf "%3d\t%s\n",$results{$foo}, $foo;
	print "$foo:$results{$foo}\n";
}

$ldap->unbind;

ldaplistgroups.pl


#!/usr/bin/perl -w
# ------------------------------------------------------------
# script:  ldaplistgroups.pl
# Author:  Brad Marshall (bmarshal@pisoftware.com)
# Date:    20000203
#
# Purpose: Lists which groups a user is in
#
# Copyright (c) 2000 Plugged In Software Pty Ltd.  All rights reserved.

use strict;
use Net::LDAP;

my($uid);
my(@groups);
my($entry);
my($host) = 'ldap.staff.plugged.com.au';

if (! $ARGV[0]) {
    print "$0: $0 username\n";
    exit 1;
}

if ($ARGV[0]) {
    $uid = $ARGV[0];
} else {
    die "Sorry, you must specify a login";
}

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

$ldap->bind;

my($mesg) = $ldap->search( base => "dc=pisoftware,dc=com",
					   filter => '(objectclass=posixGroup)');

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

foreach $entry ($mesg->entries) {
	my($tmpcn) = $entry->get('cn');
	my($tmpnum) = $entry->get('gidnumber');
	my(@members) = $entry->get('memberuid');

	#print "\@members = @members\n";
	#print "Checking $tmpcn...";

	my %t = ();
	@t{@members} = 1;
	if (exists $t{$uid}) {
		#print "yes.";
		# Push the cn onto an array
		push @groups, $tmpcn;
	}
	#print "\n";
}

$ldap->unbind;

my($groups) = join " ", @groups;
print "$uid is in $groups\n";

References

This tutorial is available from:
http://quark.humbug.org.au/publications/ldap_tut.html
http://quark.humbug.org.au/publications/ldap_tut.ps.gz

Understanding and Deploying LDAP Directory Services
Timothy A. Howes, Mark C. Smith and Gordon S. Good
Macmillan Network Architecture and Development Series

Implementing LDAP
Mark Wilcox
Wrox Press Ltd

Perl for System Administration
David N. Blank-Edelman
O'Reilly

The SLAPD and SLURPD Administrators Guide,
http://www.umich.edu/~dirsvcs/ldap/doc/guides/slapd/

PADL,
http://www.padl.com/

PADL's pam_ldap,
http://www.padl.com/pam_ldap.html

PADL's nss_ldap,
http://www.padl.com/nss_ldap.html

PADL's Migration scripts,
http://www.padl.com/tools.html

System Authentication Using LDAP
Brad Marshall
http://quark.humbug.org.au/publications/system_auth/sage-au/system_auth.html