#!/usr/bin/perl -w # by Steve Haslam # description: # adds a user into an LDAP group- where "users" and "groups" are the # objects seen by nss_ldap. # would be nice to be able to use pam-ldap here, but I've not got # that to work yet. # notes: # at least on my machine, Term::Readkey gives loads of "use of # uninitialized value" errors. ymmv. # illustrates lots of things, and a resonably nice showcase for some # techniques for working with Net::LDAP (e.g. the ldapassert() sub) require 5; use strict; use Net::LDAP; use Net::LDAP::Util qw(ldap_error_text); use Term::ReadKey; use Getopt::Std; use vars qw($hostname $basedn $ldap $sr $username $groupname $userdn $groupdn $entry $admindn $adminpw $verbose %opts); getopts('h:D:b:v', \%opts) && @ARGV == 2 or die "Syntax: $0 [-v] [-h hostname] [-D binddn] [-b basedn] username groupname"; ($username, $groupname) = (lc($ARGV[0]), lc($ARGV[1])); $verbose = 1 if ($opts{'v'}); # Print something if the -v switch was given sub printv { print(@_) if ($verbose); } # Wrapper for a Net::LDAP call- assert the server message is a "success" # code- die with a decoded error message if it isn't sub ldapassert { my $mesg = shift; my $op = shift; $mesg->code && die "LDAP error".($op?" during $op":"").": ".ldap_error_text($mesg->code)."\n"; $mesg; } # Extract a configuration option from the nss-ldap configuration file # /etc/libnss-ldap.conf is the Debian conf file location # /etc/ldap.conf is an alternative plausible location sub confoption { my $optname = lc(shift); my $conffile; foreach $conffile (qw|/etc/libnss-ldap.conf /etc/ldap.conf|) { if (-f $conffile) { open(LDAPCONF, $conffile) or die "Unable to open nss-ldap configuration file $conffile: $!\n"; while () { s/\#.*//; chomp; my($keyword, $value) = split(/ +/, $_, 2); next unless (defined($keyword) && defined($value)); $keyword = lc($keyword); if ($keyword eq $optname) { close(LDAPCONF); printv "[ldapconf $conffile] using \"$value\" for \"$optname\"\n"; return $value; } } return undef; } } printv "[ldapconf] no value for \"$optname\"\n"; return undef; } $hostname = $opts{'h'} || confoption('host'); $basedn = $opts{'b'} || confoption('base'); $ldap = Net::LDAP->new($hostname) or die "$@"; # Bind as administrator user $admindn = $opts{'D'} || confoption('binddn'); # Get admin password iff a bind dn was specified if ($admindn) { print "LDAP password: "; ReadMode('noecho'); $adminpw = ReadLine; chomp($adminpw); ReadMode(0); print "\n"; } # Perform bind # bind anonymously if no pw given if ($adminpw) { printv "Binding as $admindn\n"; ldapassert($ldap->bind(dn => $admindn, password => $adminpw), "bind"); } else { printv "Binding anonymously\n"; ldapassert($ldap->bind, "anonymous bind"); } # Find the user- get the user dn $sr = ldapassert($ldap->search(base => "ou=People, $basedn", filter => "(&(objectClass=posixAccount)(uid=$username))"), "user search"); if ($sr->count == 0) { die "Unknown user '$username'\n"; } elsif ($sr->count > 1) { die "Ambiguous user '$username' (this is really bad)\n"; } $entry = $sr->shift_entry; $userdn = $entry->dn; # Find the group- get the group dn $sr = ldapassert($ldap->search(base => "ou=Group, $basedn", filter => "(&(objectClass=posixGroup)(cn=$groupname))"), "group search"); if ($sr->count == 0) { die "Unknown group '$groupname'\n"; } elsif ($sr->count > 1) { die "Ambiguous group '$groupname' (this is really bad)\n"; } $entry = $sr->shift_entry; $groupdn = $entry->dn; # Is the user already in the group? foreach (@{$entry->get('memberuid')}) { if (lc($_) eq lc($username)) { print "$username is already a member of $groupname\n"; exit(0); } } # OK, now update the group entry # $entry is the group entry printv "Adding [$userdn] to [$groupdn]\n"; $entry->add(memberuid => $username); ldapassert($entry->update($ldap), "update"); # Write updated entry to directory server exit(0);