#!/usr/bin/perl -w # Copyright 2003 by intrigeri@squat.net # Licensed to the public under the terms of the GNU GPL, version 2 # Heavily modified by intrigeri@boum.org for http://print.squat.net # Latest version is on https://wiki.boum.org/ConnectFr/GensDansLdap # # This is intended to be a replacement for the standard "deluser" # script using a LDAP database for user authentication and information # storage. It requires at a minimum that schemas are provided for : # objectClass: posixAccount # objectClass: shadowAccount # Exit codes: # # 0: Success # 1: User specified showed up more than once in the database # 2: Attempting to delete home directories without being root # 3: No username specified, or showhelp was called. # 4: LDAP del (user) failed # 5: LDAP modify (group) failed # Changelog # # intrigeri - 2003 09 25 - version 1.0 # - user is now deleted from the groups he/she used to be a member of # intrigeri - 2003 07 25 - version 0.1 # TODO # - manage opt_quiet, STDERR and print less erraticlay ;) # - check if ldap->unbind is called each time the program exits use strict; use warnings; use English; use Digest::MD5 qw(md5_base64); use Getopt::Long qw(:config bundling); # the "bundling" option sets short options case sensitive use Net::LDAP; use Net::LDAP::Entry; use Term::ReadPassword; # General Configuration Options my $version = 1.0; my $dhome = "/home/nfs"; # Default home directory base my $maildir = "/var/mail"; # Where mail spool files are stored # LDAP Variables my $ldap; # LDAP connection my $ldaphost; # LDAP server FQDN or IP address my $ldapversion;# LDAP version (2 or 3) my $userfilter; # Filter string to show just user account entries. my $groupfilter;# Filter string to show just group entries my $result; # Error handling variable my $search; # Stores results from LDAP searches my $entry; # Individual LDAP entry my $bindcn; # CN to bind with to authorize adding an entry my $bindpass; # LDAP Bind Password my $base; # Base under which everything is stored my $userou; # Organizational unit under which user accounts are stored my $groupou; # Organizational unit under which group information is stored my $userbase; # Complete search base for searching for users my $groupbase; # Complete search base for searching for groups my $cn; # User entry cn $ldaphost = "127.0.0.1"; $ldapversion = 3; $userfilter = "(objectClass=posixAccount)"; $groupfilter = "(objectClass=posixGroup)"; $bindcn = "cn=admin,dc=tanneries,dc=taz"; $base = "dc=tanneries,dc=taz"; $userou = "ou=People"; $groupou = "ou=Group"; if(defined $userou) { $userbase = $userou.",".$base; } else { $userbase = $base; } if(defined $groupou) { $groupbase = $groupou.",".$base; } else { $groupbase = $base; } # User data variables my $username; # Username my $uidnumber; # Numeric user id my $gidnumber; # Numeric group id my $groupinfo; # Group information from User::grent; my $group; # Group name matching group ID my $shell; # Login shell my $homedir; # Home directory # Potential command line variables my $opt_help; my $opt_deletehome; my $opt_deletemail; my $opt_quiet; my $opt_verbose; my $opt_simulate; GetOptions("help|h|?" => sub { showhelp() }, "delete-home" => \$opt_deletehome, "delete-mail" => \$opt_deletemail, "quiet|q" => \$opt_quiet, "verbose|v" => \$opt_verbose, "simulate|no-act" => \$opt_simulate, ); if( ($EUID != 0) and ( (defined $opt_deletehome) or (defined $opt_deletemail) ) ) { print STDERR "You must have root permissions to delete home directories\n"; print STDERR "or mail spools."; exit 2; } # After the option parsing is finished, each leftover argument is a # username to be handled. Those get shifted out of the @ARGV array # one at a time into the scalar variable $username and then processed. # # Do not be confused by the use of "uid" in LDAP filters -- in the # LDAP database uid refers to the username, uidNumber refers to the # numeric user ID, and gidNumber refers to the numeric group ID. # # If @ARGV contains no remaining entries, print help and exit # if(scalar(@ARGV) == 0) { print STDERR "You must specify a username.\n\n"; showhelp(); } $ldap = Net::LDAP->new($ldaphost,version => $ldapversion) or die $!; $result = $ldap->start_tls(); die $result->error() if $result->code(); $bindpass = read_password("LDAP Bind Password: "); $result = $ldap->bind($bindcn, password => $bindpass); die $result->error() if $result->code(); while(@ARGV) { $username = shift @ARGV; $search = $ldap->search(base => $userbase, scope => "one", filter => "(&(uid=".$username.")".$userfilter.")" ); die $search->error() if $search->code(); if($search->count > 1) { print STDERR "More than one entry found for (&(uid=$username)$userfilter)\n"; print STDERR "This should not happen. Please check that the LDAP filter you specified\n"; print STDERR "is correct, and that your database is not corrupted.\n"; exit 1; } if($search->count < 1) { print STDERR "The specified username ($username) doesn't exist in the database. Skipping.\n"; next; } $cn = $username; # l'objet Net::LDAP::Entry qui sera effacé ensuite my $userentry = $search->entry(0); # Créer l'entrée modifiée pour chaque groupe à modifier. # L'écriture effective dans la base sera faite ensuite. # # ALGO # pour chaque groupe # si (l'user à supprimer appartient au groupe) alors # le supprimer du groupe # fin si # fin $search = $ldap->search(base => $groupbase, scope => "one", filter => $groupfilter ); die $search->error() if $search->code(); my @groups_to_update; foreach my $groupentry ($search->all_entries) { if( user_in_group($groupentry, $username) ) { $groupentry->changetype('modify'); $groupentry->delete('memberUid' => [$username]); @groups_to_update = (@groups_to_update, $groupentry); } } # Écriture effective dans la base, etc. if(!defined $opt_simulate) { $result = $ldap->delete($userentry); if( $result->code() ) { print STDERR "ERROR: LDAP del (user) returned: ".$result->error()."\n"; $ldap->unbind(); exit 4; } print "Account ".$username." successfully removed from the database.\n" if !defined $opt_quiet; foreach my $groupentry (@groups_to_update) { $result = $groupentry->update($ldap); if( $result->code() ) { print STDERR "ERROR: LDAP modify (group ".$groupentry->get_value('cn').") returned: ".$result->error()."\n"; $ldap->unbind(); exit 5; } print "Account ".$username." successfully removed from group ".$groupentry->get_value('cn') ." in the database.\n" if !defined $opt_quiet; } if(defined $opt_deletehome) { $homedir = $dhome."/".$username; if(-e $homedir) { system("rm -rf ".$homedir); print STDERR "Home directory deleted.\n" if !defined $opt_quiet; } else { if(!defined $opt_quiet) { print STDERR "WARNING: ".$homedir." does not exist.\n"; print STDERR "Not removing home directory.\n"; } } } if(defined $opt_deletemail) { if(-e $maildir."/".$username) { system("rm -rf ".$maildir."/".$username); print STDERR "Mail spool succesfully removed.\n" if !defined $opt_quiet; } else { if(!defined $opt_quiet) { print STDERR "WARNING: mail spool doesn't exist at ".$maildir."/".$username.".\n"; print STDERR "Not removing mail spool.\n"; } } } } } $ldap->unbind(); exit 0; # # sub showhelp # # Displays a help message displaying usage options and then exits with # an error. # # Takes no arguments, does not return. # sub showhelp { print "To delete a normal user:\n"; print "ldapdeluser [--delete-home] [--delete-mail]\n"; print " [--verbose | --quiet]\n"; print " [--simulate] username1 username2 username3 ...\n"; print "\n"; exit 3; } # # sub user_in_group # # Takes as arguments: # - a LDAP posixGroup contained in an object Net::LDAP::Entry # - a string storing a user's name # Returns 1 if this user is a member of this group, and 0 else. # sub user_in_group { my $groupentry = shift; my $username = shift; return 0 if(group_is_empty($groupentry)); my @values = $groupentry->get_value('memberUid'); foreach my $value ( @values ) { return 1 if($value eq $username); } return 0; } # # sub group_is_empty # # Takes as argument a LDAP posixGroup contained in an object Net::LDAP::Entry. # Returns 1 if this group has no member, and 0 else. # sub group_is_empty { my $groupentry = pop; my @attributs = $groupentry->attributes(); foreach my $attribut ( @attributs ) { return 0 if($attribut eq 'memberUid') } return 1; }