8 # $Id: smbldap_tools.pm,v 1.62 2005/05/27 14:28:47 jtournier Exp $
10 # This code was developped by IDEALX (http://IDEALX.org/) and
11 # contributors (their names can be found in the CONTRIBUTORS file).
13 # Copyright (C) 2001-2002 IDEALX
15 # This program is free software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
31 # ugly funcs using global variables and spawning openldap clients
34 if (-e "/etc/smbldap-tools/smbldap.conf") {
35 $smbldap_conf="/etc/smbldap-tools/smbldap.conf";
37 $smbldap_conf="/etc/opt/IDEALX/smbldap-tools/smbldap.conf";
39 my $smbldap_bind_conf;
40 if (-e "/etc/smbldap-tools/smbldap_bind.conf") {
41 $smbldap_bind_conf="/etc/smbldap-tools/smbldap_bind.conf";
43 $smbldap_bind_conf="/etc/opt/IDEALX/smbldap-tools/smbldap_bind.conf";
46 if (-e "/etc/samba/smb.conf") {
47 $samba_conf="/etc/samba/smb.conf";
49 $samba_conf="/usr/local/samba/lib/smb.conf";
52 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
57 use vars qw(%config $ldap);
70 add_samba_machine_smbpasswd
109 print STDERR "(c) Jerome Tournier - IDEALX 2004 (http://www.idealx.com)- Licensed under the GPL\n"
110 unless $config{no_banner};
116 ## check for a param = value
119 if ($_=~/\s*.*?\s*=\s*".*"/) {
120 ($param,$val) = /\s*(.*?)\s*=\s*"(.*)"/;
121 } elsif ($_=~/\s*.*?\s*=\s*'.*'/) {
122 ($param,$val) = /\s*(.*?)\s*=\s*'(.*)'/;
124 ($param,$val) = /\s*(.*?)\s*=\s*(.*)/;
126 return ($param,$val);
135 $value =~ s/\$\{([^}]+)\}/$vars->{$1} ? $vars->{$1} : $1/eg;
142 open (CONFIGFILE, "$smbldap_conf") || die "Unable to open $smbldap_conf for reading !\n";
143 while (<CONFIGFILE>) {
145 ## throw away comments
146 next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/);
147 ## check for a param = value
148 my ($parameter,$value)=read_parameter($_);
149 $value = &subst_configvar($value, \%conf);
150 $conf{$parameter}=$value;
155 open (CONFIGFILE, "$smbldap_bind_conf") || die "Unable to open $smbldap_bind_conf for reading !\n";
156 while (<CONFIGFILE>) {
158 ## throw away comments
159 next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/);
160 ## check for a param = value
161 my ($parameter,$value)=read_parameter($_);
162 $value = &subst_configvar($value, \%conf);
163 $conf{$parameter}=$value;
167 $conf{slaveDN}=$conf{slavePw}=$conf{masterDN}=$conf{masterPw}="";
169 # automatically find SID
170 if (not $conf{SID}) {
171 $conf{SID} = getLocalSID() ||
172 die "Unable to determine domain SID: please edit your smbldap.conf,
173 or start your samba server for a few minutes to allow for SID generation to proceed\n";
181 my $smbconf="$samba_conf";
182 open (CONFIGFILE, "$smbconf") || die "Unable to open $smbconf for reading !\n";
184 while (<CONFIGFILE>) {
190 if (/^\[/ and !/\[global\]/) {
193 ## throw away comments
194 #next if ( ! /workgroup/i );
195 next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/ || /\[/);
196 ## check for a param = value
197 my ($parameter,$value)=read_parameter($_);
198 $value = &subst_configvar($value, \%conf);
199 $conf{$parameter}=$value;
206 my %smbconf=read_smbconf();
209 my $string = `LANG= PATH=/opt/IDEALX/bin:/usr/local/bin:/usr/bin:/bin net getlocalsid 2>/dev/null`;
210 my ($domain,$sid)=($string =~ m/^SID for domain (\S+) is: (\S+)$/ );
215 # let's read the configurations file...
219 # this function return the value for a parameter. The name of the parameter can be either this
220 # defined in smb.conf or smbldap.conf
221 my $parameter_smb=shift;
222 my $parameter_smbldap=shift;
223 if (defined $config{$parameter_smbldap} and $config{$parameter_smbldap} ne "") {
224 return $config{$parameter_smbldap};
225 } elsif (defined $smbconf{$parameter_smb} and $smbconf{$parameter_smb} ne "") {
226 return $smbconf{$parameter_smb};
228 #print "could not find parameter's value (parameter given: $parameter_smbldap or $parameter_smb) !!\n";
229 undef $smbconf{$parameter_smb};
234 $config{sambaDomain}=get_parameter("workgroup","sambaDomain");
235 $config{suffix}=get_parameter("ldap suffix","suffix");
236 $config{usersdn}=get_parameter("ldap user suffix","usersdn");
237 if ($config{usersdn} !~ m/,/ ) {$config{usersdn}=$config{usersdn}.",".$config{suffix};}
238 $config{groupsdn}=get_parameter("ldap group suffix","groupsdn");
239 if ($config{groupsdn} !~ m/,/ ) {$config{groupsdn}=$config{groupsdn}.",".$config{suffix};}
240 $config{computersdn}=get_parameter("ldap machine suffix","computersdn");
241 if ($config{computersdn} !~ m/,/ ) {$config{computersdn}=$config{computersdn}.",".$config{suffix};}
242 $config{idmapdn}=get_parameter("ldap idmap suffix","idmapdn");
243 if (defined $config{idmapdn}) {
244 if ($config{idmapdn} !~ m/,/ ) {$config{idmapdn}=$config{idmapdn}.",".$config{suffix};}
247 # next uidNumber and gidNumber available are stored in sambaDomainName object
248 if (!defined $config{sambaUnixIdPooldn}) {
249 $config{sambaUnixIdPooldn}="sambaDomainName=$config{sambaDomain},$config{suffix}";
251 if (!defined $config{masterLDAP}) {
252 $config{masterLDAP}="127.0.0.1";
254 if (!defined $config{masterPort}) {
255 $config{masterPort}="389";
257 if (!defined $config{slaveLDAP}) {
258 $config{slaveLDAP}="127.0.0.1";
260 if (!defined $config{slavePort}) {
261 $config{slavePort}="389";
263 if (!defined $config{ldapTLS}) {
264 $config{ldapTLS}="0";
267 sub connect_ldap_master
269 # bind to a directory with dn and password
270 my $ldap_master = Net::LDAP->new(
271 "$config{masterLDAP}",
272 port => "$config{masterPort}",
277 or die "erreur LDAP: Can't contact master ldap server ($@)";
278 if ($config{ldapTLS} == 1) {
279 $ldap_master->start_tls(
280 verify => "$config{verify}",
281 clientcert => "$config{clientcert}",
282 clientkey => "$config{clientkey}",
283 cafile => "$config{cafile}"
286 $ldap_master->bind ( "$config{masterDN}",
287 password => "$config{masterPw}"
290 return($ldap_master);
293 sub connect_ldap_slave
295 # bind to a directory with dn and password
297 my $ldap_slave = Net::LDAP->new(
298 "$config{slaveLDAP}",
299 port => "$config{slavePort}",
304 or warn "erreur LDAP: Can't contact slave ldap server ($@)\n=>trying to contact the master server\n";
306 # connection to the slave failed: trying to contact the master ...
307 $ldap_slave = Net::LDAP->new(
308 "$config{masterLDAP}",
309 port => "$config{masterPort}",
314 or die "erreur LDAP: Can't contact master ldap server ($@)\n";
317 if ($config{ldapTLS} == 1) {
318 $ldap_slave->start_tls(
319 verify => "$config{verify}",
320 clientcert => "$config{clientcert}",
321 clientkey => "$config{clientkey}",
322 cafile => "$config{cafile}"
325 $ldap_slave->bind ( "$config{masterDN}",
326 password => "$config{masterPw}"
337 my $mesg = $ldap->search ( base => $config{suffix},
338 scope => $config{scope},
339 filter => "(&(objectclass=posixAccount)(uid=$user))"
341 $mesg->code && die $mesg->error;
342 foreach my $entry ($mesg->all_entries) {
358 my $mesg = $ldap->search ( base => $config{suffix},
359 scope => $config{scope},
360 filter => "(&(objectclass=posixAccount)(uid=$user))"
362 $mesg->code && warn "failed to perform search; ", $mesg->error;
364 foreach my $entry ($mesg->all_entries) {
381 if ($group =~ /^\d+$/) {
382 $filter="(&(objectclass=posixGroup)(|(cn=$group)(gidNumber=$group)))";
384 $filter="(&(objectclass=posixGroup)(cn=$group))";
386 my $mesg = $ldap->search ( base => $config{groupsdn},
387 scope => $config{scope},
390 $mesg->code && die $mesg->error;
391 foreach my $entry ($mesg->all_entries) {
402 # return (success, dn)
403 # bool = is_samba_user($username)
407 my $mesg = $ldap->search ( base => $config{suffix},
408 scope => $config{scope},
409 filter => "(&(objectClass=sambaSamAccount)(uid=$user))"
411 $mesg->code && die $mesg->error;
412 return ($mesg->count ne 0);
418 my $mesg = $ldap->search ( base => $config{suffix},
419 scope => $config{scope},
420 filter => "(&(objectClass=posixAccount)(uid=$user))"
422 $mesg->code && die $mesg->error;
423 return ($mesg->count ne 0);
428 my $dn_group = shift;
430 my $mesg = $ldap->search ( base => $dn_group,
432 filter => "(&(memberUid=$user))"
434 $mesg->code && die $mesg->error;
435 return ($mesg->count ne 0);
438 # all entries = does_sid_exist($sid,$config{scope})
443 my $mesg = $ldap->search ( base => $dn_group,
444 scope => $config{scope},
445 filter => "(sambaSID=$sid)"
446 #filter => "(&(objectClass=sambaSAMAccount|objectClass=sambaGroupMapping)(sambaSID=$sid))"
448 $mesg->code && die $mesg->error;
452 # try to bind with user dn and password to validate current password
455 my ($user, $dn, $pass) = @_;
456 my $userLdap = Net::LDAP->new($config{slaveLDAP}) or die "erreur LDAP";
457 my $mesg= $userLdap->bind (dn => $dn, password => $pass );
458 if ($mesg->code eq 0) {
462 if ($userLdap->bind()) {
466 print ("The LDAP directory is not available.\n Check the server, cables ...");
470 die "Problem : contact your administrator";
475 # dn = get_dn_from_line ($dn_line)
476 # helper to get "a=b,c=d" from "dn: a=b,c=d"
485 # success = add_posix_machine($user, $uid, $gid)
486 sub add_posix_machine
488 my ($user,$uid,$gid,$wait) = @_;
489 if (!defined $wait) {
492 # bind to a directory with dn and password
493 my $add = $ldap->add ( "uid=$user,$config{computersdn}",
495 'objectclass' => ['top','inetOrgPerson', 'posixAccount'],
499 'uidNumber' => "$uid",
500 'gidNumber' => "$gid",
501 'homeDirectory' => '/dev/null',
502 'loginShell' => '/bin/false',
503 'description' => 'Computer',
504 'gecos' => 'Computer',
508 $add->code && warn "failed to add entry: ", $add->error ;
514 # success = add_samba_machine_smbpasswd($computername)
515 sub add_samba_machine_smbpasswd
518 system "smbpasswd -a -m $user";
522 sub add_samba_machine
524 my ($user, $uid) = @_;
525 my $sambaSID = 2 * $uid + 1000;
529 my ($lmpassword,$ntpassword) = ntlmgen $name;
530 my $modify = $ldap->modify ( "uid=$user,$config{computersdn}",
532 replace => [objectClass => ['inetOrgPerson', 'posixAccount', 'sambaSAMAccount']],
533 add => [sambaPwdLastSet => '0'],
534 add => [sambaLogonTime => '0'],
535 add => [sambaLogoffTime => '2147483647'],
536 add => [sambaKickoffTime => '2147483647'],
537 add => [sambaPwdCanChange => '0'],
538 add => [sambaPwdMustChange => '0'],
539 add => [sambaAcctFlags => '[W ]'],
540 add => [sambaLMPassword => "$lmpassword"],
541 add => [sambaNTPassword => "$ntpassword"],
542 add => [sambaSID => "$config{SID}-$sambaSID"],
543 add => [sambaPrimaryGroupSID => "$config{SID}-0"]
547 $modify->code && die "failed to add entry: ", $modify->error ;
554 my ($group, $userid) = @_;
556 my $dn_line = get_group_dn($group);
557 if (!defined(get_group_dn($group))) {
558 print "$0: group \"$group\" doesn't exist\n";
561 if (!defined($dn_line)) {
564 my $dn = get_dn_from_line("$dn_line");
565 # on look if the user is already present in the group
566 my $is_member=is_group_member($dn,$userid);
567 if ($is_member == 1) {
568 print "User \"$userid\" already member of the group \"$group\".\n";
570 # bind to a directory with dn and password
571 # It does not matter if the user already exist, Net::LDAP will add the user
572 # if he does not exist, and ignore him if his already in the directory.
573 my $modify = $ldap->modify ( "$dn",
575 add => [memberUid => $userid]
578 $modify->code && die "failed to modify entry: ", $modify->error ;
585 # bind to a directory with dn and password
586 my $modify = $ldap->delete ($group_dn);
587 $modify->code && die "failed to delete group : ", $modify->error ;
590 sub add_grouplist_user
592 my ($grouplist, $user) = @_;
593 my @array = split(/,/, $grouplist);
594 foreach my $group (@array) {
595 group_add_user($group, $user);
603 my $dn = get_dn_from_line($dn_line);
605 if (!defined($dn_line = get_user_dn($user))) {
606 print "$0: user $user doesn't exist\n";
609 my $modify = $ldap->modify ( "$dn",
611 replace => [userPassword => '{crypt}!x']
614 $modify->code && die "failed to modify entry: ", $modify->error ;
616 if (is_samba_user($user)) {
617 my $modify = $ldap->modify ( "$dn",
619 replace => [sambaAcctFlags => '[D ]']
622 $modify->code && die "failed to modify entry: ", $modify->error ;
632 if (!defined($dn_line = get_user_dn($user))) {
633 print "$0: user $user doesn't exist\n";
637 my $dn = get_dn_from_line($dn_line);
638 my $modify = $ldap->delete($dn);
641 # $gid = group_add($groupname, $group_gid, $force_using_existing_gid)
644 my ($gname, $gid, $force) = @_;
645 my $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1";
646 if ($nscd_status == 0) {
647 system "/etc/init.d/nscd stop > /dev/null 2>&1";
649 if (!defined($gid)) {
650 #while (defined(getgrgid($config{GID_START}))) {
651 # $config{GID_START}++;
653 #$gid = $config{GID_START};
654 $gid=get_next_id($config{groupsdn},"gidNumber");
656 if (!defined($force)) {
657 if (defined(getgrgid($gid))) {
662 if ($nscd_status == 0) {
663 system "/etc/init.d/nscd start > /dev/null 2>&1";
665 my $modify = $ldap->add ( "cn=$gname,$config{groupsdn}",
667 objectClass => 'posixGroup',
673 $modify->code && die "failed to add entry: ", $modify->error ;
677 # $homedir = get_homedir ($user)
683 my $mesg = $ldap->search (
684 base =>$config{usersdn},
685 scope => $config{scope},
686 filter => "(&(objectclass=posixAccount)(uid=$user))"
688 $mesg->code && die $mesg->error;
692 print "Aborting: there are $nb existing user named $user\n";
693 foreach $entry ($mesg->all_entries) {
699 $entry = $mesg->shift_entry();
700 $homeDir= $entry->get_value("homeDirectory");
704 if ($homeDir eq '') {
715 my $mesg = $ldap->search ( # perform a search
716 base => $config{suffix},
717 scope => $config{scope},
718 filter => "(&(objectclass=posixAccount)(uid=$user))"
721 $mesg->code && die $mesg->error;
722 foreach my $entry ($mesg->all_entries) {
723 $lines.= "dn: " . $entry->dn."\n";
724 foreach my $attr ($entry->attributes) {
726 $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
738 # return the attributes in an array
742 my $mesg = $ldap->search ( # perform a search
743 base => $config{suffix},
744 scope => $config{scope},
745 filter => "(&(objectclass=posixAccount)(uid=$user))"
748 $mesg->code && die $mesg->error;
749 my $entry = $mesg->entry();
758 my $mesg = $ldap->search ( # perform a search
759 base => $config{groupsdn},
760 scope => $config{scope},
761 filter => "(&(objectclass=posixGroup)(cn=$user))"
764 $mesg->code && die $mesg->error;
765 foreach my $entry ($mesg->all_entries) {
766 $lines.= "dn: " . $entry->dn."\n";
767 foreach my $attr ($entry->attributes) {
769 $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
780 # find groups of a given user
781 ##### MODIFIE ########
785 my $mesg = $ldap->search ( # perform a search
786 base => $config{groupsdn},
787 scope => $config{scope},
788 filter => "(&(objectclass=posixGroup)(memberuid=$user))"
790 $mesg->code && die $mesg->error;
793 while ($entry = $mesg->shift_entry()) {
794 push(@groups, scalar($entry->get_value('cn')));
799 sub read_group_entry {
803 my $mesg = $ldap->search ( # perform a search
804 base => $config{groupsdn},
805 scope => $config{scope},
806 filter => "(&(objectclass=posixGroup)(cn=$group))"
809 $mesg->code && die $mesg->error;
812 print "Error: $nb groups exist \"cn=$group\"\n";
813 foreach $entry ($mesg->all_entries) {
814 my $dn=$entry->dn; print " $dn\n";
818 $entry = $mesg->shift_entry();
823 sub read_group_entry_gid {
826 my $mesg = $ldap->search ( # perform a search
827 base => $config{groupsdn},
828 scope => $config{scope},
829 filter => "(&(objectclass=posixGroup)(gidNumber=$group))"
832 $mesg->code && die $mesg->error;
833 my $entry = $mesg->shift_entry();
837 # return the gidnumber for a group given as name or gid
838 # -1 : bad group name
842 my $userGidNumber = shift;
843 if ($userGidNumber =~ /[^\d]/ ) {
844 my $gname = $userGidNumber;
845 my $gidnum = getgrnam($gname);
846 if ($gidnum !~ /\d+/) {
849 $userGidNumber = $gidnum;
851 } elsif (!defined(getgrgid($userGidNumber))) {
854 return $userGidNumber;
857 # remove $user from $group
858 sub group_remove_member
860 my ($group, $user) = @_;
862 my $grp_line = get_group_dn($group);
863 if (!defined($grp_line)) {
866 my $dn = get_dn_from_line($grp_line);
867 # we test if the user exist in the group
868 my $is_member=is_group_member($dn,$user);
869 if ($is_member == 1) {
870 # delete only the user from the group
871 my $modify = $ldap->modify ( "$dn",
873 delete => [memberUid => ["$user"]]
876 $modify->code && die "failed to delete entry: ", $modify->error ;
881 sub group_get_members
886 my $grp_line = get_group_dn($group);
887 if (!defined($grp_line)) {
890 my $mesg = $ldap->search (
891 base => $config{groupsdn},
892 scope => $config{scope},
893 filter => "(&(objectclass=posixgroup)(cn=$group))"
895 $mesg->code && die $mesg->error;
896 foreach my $entry ($mesg->all_entries) {
897 foreach my $attr ($entry->attributes) {
898 if ($attr=~/\bmemberUid\b/) {
899 foreach my $ent ($entry->get_value($attr)) {
900 push (@resultat,$ent);
911 my $FILE = "|$config{ldapmodify} -r >/dev/null";
912 open (FILE, $FILE) || die "$!\n";
922 sub group_type_by_name {
923 my $type_name = shift;
929 return $groupmap{$type_name};
934 my ($str, $username) = @_;
935 $str =~ s/%U/$username/ if ($str);
939 # all given mails are stored in a table (remove the comma separated)
940 sub split_arg_comma {
947 @args = split(/\s*,\s*/, $arg);
954 my ($list1, $list2) = @_;
956 foreach my $e (@$list2) {
957 if (! grep($_ eq $e, @$list1)) {
965 my ($list1, $list2) = @_;
967 foreach my $e (@$list1) {
968 if (! grep( $_ eq $e, @$list2 )) {
975 sub get_next_id($$) {
976 my $ldap_base_dn = shift;
977 my $attribute = shift;
982 if ($ldap_base_dn =~ m/$config{usersdn}/i) {
983 # when adding a new user, we'll check if the uidNumber available is not
984 # already used for a computer's account
985 $ldap_base_dn=$config{suffix}
988 $next_uid_mesg = $ldap->search(
989 base => $config{sambaUnixIdPooldn},
990 filter => "(objectClass=sambaUnixIdPool)",
993 $next_uid_mesg->code && die "Error looking for next uid";
994 if ($next_uid_mesg->count != 1) {
995 die "Could not find base dn, to get next $attribute";
997 my $entry = $next_uid_mesg->entry(0);
999 $nextuid = $entry->get_value($attribute);
1000 my $modify=$ldap->modify( "$config{sambaUnixIdPooldn}",
1002 replace => [ $attribute => $nextuid + 1 ]
1005 $modify->code && die "Error: ", $modify->error;
1006 # let's check if the id found is really free (in ou=Groups or ou=Users)...
1007 my $check_uid_mesg = $ldap->search(
1008 base => $ldap_base_dn,
1009 filter => "($attribute=$nextuid)",
1011 $check_uid_mesg->code && die "Cannot confirm $attribute $nextuid is free";
1012 if ($check_uid_mesg->count == 0) {
1017 print "Cannot confirm $attribute $nextuid is free: checking for the next one\n"
1018 } while ($found != 1);
1019 die "Could not allocate $attribute!";