#!/usr/bin/perl -w use strict; package smbldap_tools; use Net::LDAP; use Crypt::SmbHash; # $Id: smbldap_tools.pm,v 1.54 2005/01/29 15:00:54 jtournier Exp $ # # This code was developped by IDEALX (http://IDEALX.org/) and # contributors (their names can be found in the CONTRIBUTORS file). # # Copyright (C) 2001-2002 IDEALX # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # ugly funcs using global variables and spawning openldap clients my $smbldap_conf="/etc/opt/IDEALX/smbldap-tools/smbldap.conf"; my $smbldap_bind_conf="/etc/opt/IDEALX/smbldap-tools/smbldap_bind.conf"; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); use Exporter; $VERSION = 1.00; @ISA = qw(Exporter); use vars qw(%config $ldap); @EXPORT = qw( get_user_dn get_group_dn is_group_member is_samba_user is_unix_user is_user_valid does_sid_exist get_dn_from_line add_posix_machine add_samba_machine add_samba_machine_smbpasswd group_add_user add_grouplist_user disable_user delete_user group_add group_del get_homedir read_user read_user_entry read_group read_group_entry read_group_entry_gid find_groups_of parse_group group_remove_member group_get_members do_ldapadd do_ldapmodify get_user_dn2 connect_ldap_master connect_ldap_slave group_type_by_name subst_configvar read_config read_parameter subst_user split_arg_comma list_union list_minus get_next_id print_banner %config ); sub print_banner { print STDERR "(c) IDEALX 2004 (http://www.idealx.com)- Licenced under GPL\n"; } sub read_parameter { my $line=shift; ## check for a param = value if ($_=~/=/) { my ($param,$val); if ($_=~/"/) { #my ($param,$val) = ($_=~/(.*)\s*=\s*"(.*)"/); ($param,$val) = /\s*(.*?)\s*=\s*"(.*)"/; } elsif ($_=~/'/) { ($param,$val) = /\s*(.*?)\s*=\s*'(.*)'/; } else { ($param,$val) = /\s*(.*?)\s*=\s*(.*)/; } return ($param,$val); } } sub subst_configvar { my $value = shift; my $vars = shift; $value =~ s/\$\{([^}]+)\}/$vars->{$1} ? $vars->{$1} : $1/eg; return $value; } sub read_conf { my %conf; open (CONFIGFILE, "$smbldap_conf") || die "Unable to open $smbldap_conf for reading !\n"; while () { chomp($_); ## throw away comments next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/); ## check for a param = value my ($parameter,$value)=read_parameter($_); $value = &subst_configvar($value, \%conf); $conf{$parameter}=$value; } close (CONFIGFILE); if ($< == 0) { open (CONFIGFILE, "$smbldap_bind_conf") || die "Unable to open $smbldap_bind_conf for reading !\n"; while () { chomp($_); ## throw away comments next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/); ## check for a param = value my ($parameter,$value)=read_parameter($_); $value = &subst_configvar($value, \%conf); $conf{$parameter}=$value; } close (CONFIGFILE); } else { $conf{slaveDN}=$conf{slavePw}=$conf{masterDN}=$conf{masterPw}=""; } return(%conf); } # let's read the configurations file... %config=read_conf(); sub connect_ldap_master { # bind to a directory with dn and password my $ldap_master = Net::LDAP->new( "$config{masterLDAP}", port => "$config{masterPort}", version => 3, timeout => 60, # debug => 0xffff, ) or die "erreur LDAP: Can't contact master ldap server ($@)"; if ($config{ldapTLS} == 1) { $ldap_master->start_tls( verify => "$config{verify}", clientcert => "$config{clientcert}", clientkey => "$config{clientkey}", cafile => "$config{cafile}" ); } $ldap_master->bind ( "$config{masterDN}", password => "$config{masterPw}" ); $ldap=$ldap_master; return($ldap_master); } sub connect_ldap_slave { # bind to a directory with dn and password my $conf_cert; my $ldap_slave = Net::LDAP->new( "$config{slaveLDAP}", port => "$config{slavePort}", version => 3, timeout => 60, # debug => 0xffff, ) or warn "erreur LDAP: Can't contact slave ldap server ($@)\n=>trying to contact the master server\n"; if (!$ldap_slave) { # connection to the slave failed: trying to contact the master ... $ldap_slave = Net::LDAP->new( "$config{masterLDAP}", port => "$config{masterPort}", version => 3, timeout => 60, # debug => 0xffff, ) or die "erreur LDAP: Can't contact master ldap server ($@)\n"; } if ($ldap_slave) { if ($config{ldapTLS} == 1) { $ldap_slave->start_tls( verify => "$config{verify}", clientcert => "$config{clientcert}", clientkey => "$config{clientkey}", cafile => "$config{cafile}" ); } $ldap_slave->bind ( "$config{masterDN}", password => "$config{masterPw}" ); $ldap=$ldap_slave; return($ldap_slave); } } sub get_user_dn { my $user = shift; my $dn=''; my $mesg = $ldap->search ( base => $config{suffix}, scope => $config{scope}, filter => "(&(objectclass=posixAccount)(uid=$user))" ); $mesg->code && die $mesg->error; foreach my $entry ($mesg->all_entries) { $dn= $entry->dn; } chomp($dn); if ($dn eq '') { return undef; } $dn="dn: ".$dn; return $dn; } sub get_user_dn2 { my $user = shift; my $dn=''; my $mesg = $ldap->search ( base => $config{suffix}, scope => $config{scope}, filter => "(&(objectclass=posixAccount)(uid=$user))" ); $mesg->code && warn "failed to perform search; ", $mesg->error; foreach my $entry ($mesg->all_entries) { $dn= $entry->dn; } chomp($dn); if ($dn eq '') { return (1,undef); } $dn="dn: ".$dn; return (1,$dn); } sub get_group_dn { my $group = shift; my $dn=''; my $filter; if ($group =~ /^\d+$/) { $filter="(&(objectclass=posixGroup)(|(cn=$group)(gidNumber=$group)))"; } else { $filter="(&(objectclass=posixGroup)(cn=$group))"; } my $mesg = $ldap->search ( base => $config{groupsdn}, scope => $config{scope}, filter => $filter ); $mesg->code && die $mesg->error; foreach my $entry ($mesg->all_entries) { $dn= $entry->dn; } chomp($dn); if ($dn eq '') { return undef; } $dn="dn: ".$dn; return $dn; } # return (success, dn) # bool = is_samba_user($username) sub is_samba_user { my $user = shift; my $mesg = $ldap->search ( base => $config{suffix}, scope => $config{scope}, filter => "(&(objectClass=sambaSamAccount)(uid=$user))" ); $mesg->code && die $mesg->error; return ($mesg->count ne 0); } sub is_unix_user { my $user = shift; my $mesg = $ldap->search ( base => $config{suffix}, scope => $config{scope}, filter => "(&(objectClass=posixAccount)(uid=$user))" ); $mesg->code && die $mesg->error; return ($mesg->count ne 0); } sub is_group_member { my $dn_group = shift; my $user = shift; my $mesg = $ldap->search ( base => $dn_group, scope => 'base', filter => "(&(memberUid=$user))" ); $mesg->code && die $mesg->error; return ($mesg->count ne 0); } # all entries = does_sid_exist($sid,$config{scope}) sub does_sid_exist { my $sid = shift; my $dn_group=shift; my $mesg = $ldap->search ( base => $dn_group, scope => $config{scope}, filter => "(sambaSID=$sid)" #filter => "(&(objectClass=sambaSAMAccount|objectClass=sambaGroupMapping)(sambaSID=$sid))" ); $mesg->code && die $mesg->error; return ($mesg); } # try to bind with user dn and password to validate current password sub is_user_valid { my ($user, $dn, $pass) = @_; my $userLdap = Net::LDAP->new($config{slaveLDAP}) or die "erreur LDAP"; my $mesg= $userLdap->bind (dn => $dn, password => $pass ); if ($mesg->code eq 0) { $userLdap->unbind; return 1; } else { if ($userLdap->bind()) { $userLdap->unbind; return 0; } else { print ("The LDAP directory is not available.\n Check the server, cables ..."); $userLdap->unbind; return 0; } die "Problem : contact your administrator"; } } # dn = get_dn_from_line ($dn_line) # helper to get "a=b,c=d" from "dn: a=b,c=d" sub get_dn_from_line { my $dn = shift; $dn =~ s/^dn: //; return $dn; } # success = add_posix_machine($user, $uid, $gid) sub add_posix_machine { my ($user, $uid, $gid) = @_; # bind to a directory with dn and password my $add = $ldap->add ( "uid=$user,$config{computersdn}", attr => [ 'objectclass' => ['top','inetOrgPerson', 'posixAccount'], 'cn' => "$user", 'sn' => "$user", 'uid' => "$user", 'uidNumber' => "$uid", 'gidNumber' => "$gid", 'homeDirectory' => '/dev/null', 'loginShell' => '/bin/false', 'description' => 'Computer', 'gecos' => 'Computer', ] ); $add->code && warn "failed to add entry: ", $add->error ; # take down the session return 1; } # success = add_samba_machine_smbpasswd($computername) sub add_samba_machine_smbpasswd { my $user = shift; system "smbpasswd -a -m $user"; return 1; } sub add_samba_machine { my ($user, $uid) = @_; my $sambaSID = 2 * $uid + 1000; my $name = $user; $name =~ s/.$//s; my ($lmpassword,$ntpassword) = ntlmgen $name; my $modify = $ldap->modify ( "uid=$user,$config{computersdn}", changes => [ replace => [objectClass => ['inetOrgPerson', 'posixAccount', 'sambaSAMAccount']], add => [sambaPwdLastSet => '0'], add => [sambaLogonTime => '0'], add => [sambaLogoffTime => '2147483647'], add => [sambaKickoffTime => '2147483647'], add => [sambaPwdCanChange => '0'], add => [sambaPwdMustChange => '0'], add => [sambaAcctFlags => '[W ]'], add => [sambaLMPassword => "$lmpassword"], add => [sambaNTPassword => "$ntpassword"], add => [sambaSID => "$config{SID}-$sambaSID"], add => [sambaPrimaryGroupSID => "$config{SID}-0"] ] ); $modify->code && die "failed to add entry: ", $modify->error ; return 1; } sub group_add_user { my ($group, $userid) = @_; my $members=''; my $dn_line = get_group_dn($group); if (!defined(get_group_dn($group))) { print "$0: group \"$group\" doesn't exist\n"; exit (6); } if (!defined($dn_line)) { return 1; } my $dn = get_dn_from_line("$dn_line"); # on look if the user is already present in the group my $is_member=is_group_member($dn,$userid); if ($is_member == 1) { print "User \"$userid\" already member of the group \"$group\".\n"; } else { # bind to a directory with dn and password # It does not matter if the user already exist, Net::LDAP will add the user # if he does not exist, and ignore him if his already in the directory. my $modify = $ldap->modify ( "$dn", changes => [ add => [memberUid => $userid] ] ); $modify->code && die "failed to modify entry: ", $modify->error ; } } sub group_del { my $group_dn=shift; # bind to a directory with dn and password my $modify = $ldap->delete ($group_dn); $modify->code && die "failed to delete group : ", $modify->error ; } sub add_grouplist_user { my ($grouplist, $user) = @_; my @array = split(/,/, $grouplist); foreach my $group (@array) { group_add_user($group, $user); } } sub disable_user { my $user = shift; my $dn_line; my $dn = get_dn_from_line($dn_line); if (!defined($dn_line = get_user_dn($user))) { print "$0: user $user doesn't exist\n"; exit (10); } my $modify = $ldap->modify ( "$dn", changes => [ replace => [userPassword => '{crypt}!x'] ] ); $modify->code && die "failed to modify entry: ", $modify->error ; if (is_samba_user($user)) { my $modify = $ldap->modify ( "$dn", changes => [ replace => [sambaAcctFlags => '[D ]'] ] ); $modify->code && die "failed to modify entry: ", $modify->error ; } } # delete_user($user) sub delete_user { my $user = shift; my $dn_line; if (!defined($dn_line = get_user_dn($user))) { print "$0: user $user doesn't exist\n"; exit (10); } my $dn = get_dn_from_line($dn_line); my $modify = $ldap->delete($dn); } # $gid = group_add($groupname, $group_gid, $force_using_existing_gid) sub group_add { my ($gname, $gid, $force) = @_; my $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1"; if ($nscd_status == 0) { system "/etc/init.d/nscd stop > /dev/null 2>&1"; } if (!defined($gid)) { #while (defined(getgrgid($config{GID_START}))) { # $config{GID_START}++; #} #$gid = $config{GID_START}; $gid=get_next_id($config{groupsdn},"gidNumber"); } else { if (!defined($force)) { if (defined(getgrgid($gid))) { return undef; } } } if ($nscd_status == 0) { system "/etc/init.d/nscd start > /dev/null 2>&1"; } my $modify = $ldap->add ( "cn=$gname,$config{groupsdn}", attrs => [ objectClass => 'posixGroup', cn => "$gname", gidNumber => "$gid" ] ); $modify->code && die "failed to add entry: ", $modify->error ; return $gid; } # $homedir = get_homedir ($user) sub get_homedir { my $user = shift; my $homeDir=''; my $entry; my $mesg = $ldap->search ( base =>$config{usersdn}, scope => $config{scope}, filter => "(&(objectclass=posixAccount)(uid=$user))" ); $mesg->code && die $mesg->error; my $nb=$mesg->count; if ($nb > 1) { print "Aborting: there are $nb existing user named $user\n"; foreach $entry ($mesg->all_entries) { my $dn=$entry->dn; print " $dn\n"; } exit (4); } else { $entry = $mesg->shift_entry(); $homeDir= $entry->get_value("homeDirectory"); } chomp $homeDir; if ($homeDir eq '') { return undef; } return $homeDir; } # search for an user sub read_user { my $user = shift; my $lines =''; my $mesg = $ldap->search ( # perform a search base => $config{suffix}, scope => $config{scope}, filter => "(&(objectclass=posixAccount)(uid=$user))" ); $mesg->code && die $mesg->error; foreach my $entry ($mesg->all_entries) { $lines.= "dn: " . $entry->dn."\n"; foreach my $attr ($entry->attributes) { { $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n"; } } } chomp $lines; if ($lines eq '') { return undef; } return $lines; } # search for a user # return the attributes in an array sub read_user_entry { my $user = shift; my $mesg = $ldap->search ( # perform a search base => $config{suffix}, scope => $config{scope}, filter => "(&(objectclass=posixAccount)(uid=$user))" ); $mesg->code && die $mesg->error; my $entry = $mesg->entry(); return $entry; } # search for a group sub read_group { my $user = shift; my $lines =''; my $mesg = $ldap->search ( # perform a search base => $config{groupsdn}, scope => $config{scope}, filter => "(&(objectclass=posixGroup)(cn=$user))" ); $mesg->code && die $mesg->error; foreach my $entry ($mesg->all_entries) { $lines.= "dn: " . $entry->dn."\n"; foreach my $attr ($entry->attributes) { { $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n"; } } } chomp $lines; if ($lines eq '') { return undef; } return $lines; } # find groups of a given user ##### MODIFIE ######## sub find_groups_of { my $user = shift; my @groups = (); my $mesg = $ldap->search ( # perform a search base => $config{groupsdn}, scope => $config{scope}, filter => "(&(objectclass=posixGroup)(memberuid=$user))" ); $mesg->code && die $mesg->error; my $entry; while ($entry = $mesg->shift_entry()) { push(@groups, scalar($entry->get_value('cn'))); } return (@groups); } sub read_group_entry { my $group = shift; my $entry; my %res; my $mesg = $ldap->search ( # perform a search base => $config{groupsdn}, scope => $config{scope}, filter => "(&(objectclass=posixGroup)(cn=$group))" ); $mesg->code && die $mesg->error; my $nb=$mesg->count; if ($nb > 1) { print "Error: $nb groups exist \"cn=$group\"\n"; foreach $entry ($mesg->all_entries) { my $dn=$entry->dn; print " $dn\n"; } exit 11; } else { $entry = $mesg->shift_entry(); } return $entry; } sub read_group_entry_gid { my $group = shift; my %res; my $mesg = $ldap->search ( # perform a search base => $config{groupsdn}, scope => $config{scope}, filter => "(&(objectclass=posixGroup)(gidNumber=$group))" ); $mesg->code && die $mesg->error; my $entry = $mesg->shift_entry(); return $entry; } # return the gidnumber for a group given as name or gid # -1 : bad group name # -2 : bad gidnumber sub parse_group { my $userGidNumber = shift; if ($userGidNumber =~ /[^\d]/ ) { my $gname = $userGidNumber; my $gidnum = getgrnam($gname); if ($gidnum !~ /\d+/) { return -1; } else { $userGidNumber = $gidnum; } } elsif (!defined(getgrgid($userGidNumber))) { return -2; } return $userGidNumber; } # remove $user from $group sub group_remove_member { my ($group, $user) = @_; my $members=''; my $grp_line = get_group_dn($group); if (!defined($grp_line)) { return 0; } my $dn = get_dn_from_line($grp_line); # we test if the user exist in the group my $is_member=is_group_member($dn,$user); if ($is_member == 1) { # delete only the user from the group my $modify = $ldap->modify ( "$dn", changes => [ delete => [memberUid => ["$user"]] ] ); $modify->code && die "failed to delete entry: ", $modify->error ; } return 1; } sub group_get_members { my ($group) = @_; my $members; my @resultat; my $grp_line = get_group_dn($group); if (!defined($grp_line)) { return 0; } my $mesg = $ldap->search ( base => $config{groupsdn}, scope => $config{scope}, filter => "(&(objectclass=posixgroup)(cn=$group))" ); $mesg->code && die $mesg->error; foreach my $entry ($mesg->all_entries) { foreach my $attr ($entry->attributes) { if ($attr=~/\bmemberUid\b/) { foreach my $ent ($entry->get_value($attr)) { push (@resultat,$ent); } } } } return @resultat; } sub do_ldapmodify { my $ldif = shift; my $FILE = "|$config{ldapmodify} -r >/dev/null"; open (FILE, $FILE) || die "$!\n"; print FILE < 2, 'local' => 4, 'builtin' => 5 ); return $groupmap{$type_name}; } sub subst_user { my ($str, $username) = @_; $str =~ s/%U/$username/ if ($str); return($str); } # all given mails are stored in a table (remove the comma separated) sub split_arg_comma { my $arg = shift; my @args; if (defined($arg)) { if ($arg eq '-') { @args = ( ); } else { @args = split(/\s*,\s*/, $arg); } } return (@args); } sub list_union { my ($list1, $list2) = @_; my @res = @$list1; foreach my $e (@$list2) { if (! grep($_ eq $e, @$list1)) { push(@res, $e); } } return @res; } sub list_minus { my ($list1, $list2) = @_; my @res = (); foreach my $e (@$list1) { if (! grep( $_ eq $e, @$list2 )) { push(@res, $e); } } return @res; } sub get_next_id($$) { my $ldap_base_dn = shift; my $attribute = shift; my $tries = 0; my $found=0; my $next_uid_mesg; my $nextuid; if ($ldap_base_dn =~ m/$config{usersdn}/i) { # when adding a new user, we'll check if the uidNumber available is not # already used for a computer's account $ldap_base_dn=$config{suffix} } do { $next_uid_mesg = $ldap->search( base => $config{sambaUnixIdPooldn}, filter => "(objectClass=sambaUnixIdPool)", scope => "base" ); $next_uid_mesg->code && die "Error looking for next uid"; if ($next_uid_mesg->count != 1) { die "Could not find base dn, to get next $attribute"; } my $entry = $next_uid_mesg->entry(0); $nextuid = $entry->get_value($attribute); my $modify=$ldap->modify( "$config{sambaUnixIdPooldn}", changes => [ replace => [ $attribute => $nextuid + 1 ] ] ); $modify->code && die "Error: ", $modify->error; # let's check if the id found is really free (in ou=Groups or ou=Users)... my $check_uid_mesg = $ldap->search( base => $ldap_base_dn, filter => "($attribute=$nextuid)", ); $check_uid_mesg->code && die "Cannot confirm $attribute $nextuid is free"; if ($check_uid_mesg->count == 0) { $found=1; return $nextuid; } $tries++; print "Cannot confirm $attribute $nextuid is free: checking for the next one\n" } while ($found != 1); die "Could not allocate $attribute!"; } #$ldap->unbind; #$ldap->unbind; 1;