6
use Unicode::MapUTF8 qw(to_utf8 from_utf8);
9
# $Id: smbldap_tools.pm,v 1.65 2006/01/02 17:01:19 jtournier Exp $
11
# This code was developped by IDEALX (http://IDEALX.org/) and
12
# contributors (their names can be found in the CONTRIBUTORS file).
14
# Copyright (C) 2001-2002 IDEALX
16
# This program is free software; you can redistribute it and/or
17
# modify it under the terms of the GNU General Public License
18
# as published by the Free Software Foundation; either version 2
19
# of the License, or (at your option) any later version.
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
# GNU General Public License for more details.
26
# You should have received a copy of the GNU General Public License
27
# along with this program; if not, write to the Free Software
28
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
32
# ugly funcs using global variables and spawning openldap clients
35
if (-e "/etc/smbldap-tools/smbldap.conf") {
36
$smbldap_conf="/etc/smbldap-tools/smbldap.conf";
38
$smbldap_conf="/etc/opt/IDEALX/smbldap-tools/smbldap.conf";
41
my $smbldap_bind_conf;
42
if (-e "/etc/smbldap-tools/smbldap_bind.conf") {
43
$smbldap_bind_conf="/etc/smbldap-tools/smbldap_bind.conf";
45
$smbldap_bind_conf="/etc/opt/IDEALX/smbldap-tools/smbldap_bind.conf";
48
if (-e "/etc/samba/smb.conf") {
49
$samba_conf="/etc/samba/smb.conf";
51
$samba_conf="/usr/local/samba/lib/smb.conf";
54
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
59
use vars qw(%config $ldap);
73
add_samba_machine_smbpasswd
114
print STDERR "(c) Jerome Tournier - IDEALX 2004 (http://www.idealx.com)- Licensed under the GPL\n"
115
unless $config{no_banner};
121
## check for a param = value
124
if ($_=~/\s*.*?\s*=\s*".*"/) {
125
($param,$val) = /\s*(.*?)\s*=\s*"(.*)"/;
126
} elsif ($_=~/\s*.*?\s*=\s*'.*'/) {
127
($param,$val) = /\s*(.*?)\s*=\s*'(.*)'/;
129
($param,$val) = /\s*(.*?)\s*=\s*(.*)/;
131
return ($param,$val);
140
$value =~ s/\$\{([^}]+)\}/$vars->{$1} ? $vars->{$1} : $1/eg;
147
open (CONFIGFILE, "$smbldap_conf") || die "Unable to open $smbldap_conf for reading !\n";
148
while (<CONFIGFILE>) {
150
## throw away comments
151
next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/);
152
## check for a param = value
153
my ($parameter,$value)=read_parameter($_);
154
$value = &subst_configvar($value, \%conf);
155
$conf{$parameter}=$value;
160
open (CONFIGFILE, "$smbldap_bind_conf") || die "Unable to open $smbldap_bind_conf for reading !\n";
161
while (<CONFIGFILE>) {
163
## throw away comments
164
next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/);
165
## check for a param = value
166
my ($parameter,$value)=read_parameter($_);
167
$value = &subst_configvar($value, \%conf);
168
$conf{$parameter}=$value;
172
$conf{slaveDN}=$conf{slavePw}=$conf{masterDN}=$conf{masterPw}="";
174
# automatically find SID
175
if (not $conf{SID}) {
176
$conf{SID} = getLocalSID() ||
177
die "Unable to determine domain SID: please edit your smbldap.conf,
178
or start your samba server for a few minutes to allow for SID generation to proceed\n";
186
my $smbconf="$samba_conf";
187
open (CONFIGFILE, "$smbconf") || die "Unable to open $smbconf for reading !\n";
190
while (<CONFIGFILE>) {
202
if (/^\[/ and !/\[global\]/) {
205
## throw away comments
206
#next if ( ! /workgroup/i );
207
next if ( /^\s*#/ || /^\s*$/ || /^\s*\;/ || /\[/);
208
## check for a param = value
209
my ($parameter,$value)=read_parameter($_);
210
$value = &subst_configvar($value, \%conf);
211
$conf{$parameter}=$value;
218
my %smbconf=read_smbconf();
221
my $string = `LANG= PATH=/opt/IDEALX/bin:/usr/local/bin:/usr/bin:/bin net getlocalsid 2>/dev/null`;
222
my ($domain,$sid)=($string =~ m/^SID for domain (\S+) is: (\S+)$/ );
227
# let's read the configurations file...
231
# this function return the value for a parameter. The name of the parameter can be either this
232
# defined in smb.conf or smbldap.conf
233
my $parameter_smb=shift;
234
my $parameter_smbldap=shift;
235
if (defined $config{$parameter_smbldap} and $config{$parameter_smbldap} ne "") {
236
return $config{$parameter_smbldap};
237
} elsif (defined $smbconf{$parameter_smb} and $smbconf{$parameter_smb} ne "") {
238
return $smbconf{$parameter_smb};
240
#print "could not find parameter's value (parameter given: $parameter_smbldap or $parameter_smb) !!\n";
241
undef $smbconf{$parameter_smb};
246
$config{sambaDomain}=get_parameter("workgroup","sambaDomain");
247
$config{suffix}=get_parameter("ldap suffix","suffix");
248
$config{usersdn}=get_parameter("ldap user suffix","usersdn");
249
if ($config{usersdn} !~ m/,/ ) {$config{usersdn}=$config{usersdn}.",".$config{suffix};}
250
$config{groupsdn}=get_parameter("ldap group suffix","groupsdn");
251
if ($config{groupsdn} !~ m/,/ ) {$config{groupsdn}=$config{groupsdn}.",".$config{suffix};}
252
$config{computersdn}=get_parameter("ldap machine suffix","computersdn");
253
if ($config{computersdn} !~ m/,/ ) {$config{computersdn}=$config{computersdn}.",".$config{suffix};}
254
$config{idmapdn}=get_parameter("ldap idmap suffix","idmapdn");
255
if (defined $config{idmapdn}) {
256
if ($config{idmapdn} !~ m/,/ ) {$config{idmapdn}=$config{idmapdn}.",".$config{suffix};}
259
# next uidNumber and gidNumber available are stored in sambaDomainName object
260
if (!defined $config{sambaUnixIdPooldn}) {
261
$config{sambaUnixIdPooldn}="sambaDomainName=$config{sambaDomain},$config{suffix}";
263
if (!defined $config{masterLDAP}) {
264
$config{masterLDAP}="127.0.0.1";
266
if (!defined $config{masterPort}) {
267
$config{masterPort}="389";
269
if (!defined $config{slaveLDAP}) {
270
$config{slaveLDAP}="127.0.0.1";
272
if (!defined $config{slavePort}) {
273
$config{slavePort}="389";
275
if (!defined $config{ldapTLS}) {
276
$config{ldapTLS}="0";
279
sub connect_ldap_master
281
# bind to a directory with dn and password
282
my $ldap_master = Net::LDAP->new(
283
"$config{masterLDAP}",
284
port => "$config{masterPort}",
289
or die "erreur LDAP: Can't contact master ldap server ($@)";
290
if ($config{ldapTLS} == 1) {
291
$ldap_master->start_tls(
292
verify => "$config{verify}",
293
clientcert => "$config{clientcert}",
294
clientkey => "$config{clientkey}",
295
cafile => "$config{cafile}"
298
$ldap_master->bind ( "$config{masterDN}",
299
password => "$config{masterPw}"
302
return($ldap_master);
305
sub connect_ldap_slave
307
# bind to a directory with dn and password
309
my $ldap_slave = Net::LDAP->new(
310
"$config{slaveLDAP}",
311
port => "$config{slavePort}",
316
or warn "erreur LDAP: Can't contact slave ldap server ($@)\n=>trying to contact the master server\n";
318
# connection to the slave failed: trying to contact the master ...
319
$ldap_slave = Net::LDAP->new(
320
"$config{masterLDAP}",
321
port => "$config{masterPort}",
326
or die "erreur LDAP: Can't contact master ldap server ($@)\n";
329
if ($config{ldapTLS} == 1) {
330
$ldap_slave->start_tls(
331
verify => "$config{verify}",
332
clientcert => "$config{clientcert}",
333
clientkey => "$config{clientkey}",
334
cafile => "$config{cafile}"
337
$ldap_slave->bind ( "$config{masterDN}",
338
password => "$config{masterPw}"
349
my $mesg = $ldap->search ( base => $config{suffix},
350
scope => $config{scope},
351
filter => "(&(objectclass=posixAccount)(uid=$user))"
353
$mesg->code && die $mesg->error;
354
foreach my $entry ($mesg->all_entries) {
370
my $mesg = $ldap->search ( base => $config{suffix},
371
scope => $config{scope},
372
filter => "(&(objectclass=posixAccount)(uid=$user))"
374
$mesg->code && warn "failed to perform search; ", $mesg->error;
376
foreach my $entry ($mesg->all_entries) {
393
if ($group =~ /^\d+$/) {
394
$filter="(&(objectclass=posixGroup)(|(cn=$group)(gidNumber=$group)))";
396
$filter="(&(objectclass=posixGroup)(cn=$group))";
398
my $mesg = $ldap->search ( base => $config{groupsdn},
399
scope => $config{scope},
402
$mesg->code && die $mesg->error;
403
foreach my $entry ($mesg->all_entries) {
414
# return (success, dn)
415
# bool = is_samba_user($username)
419
my $mesg = $ldap->search ( base => $config{suffix},
420
scope => $config{scope},
421
filter => "(&(objectClass=sambaSamAccount)(uid=$user))"
423
$mesg->code && die $mesg->error;
424
return ($mesg->count ne 0);
430
my $mesg = $ldap->search ( base => $config{suffix},
431
scope => $config{scope},
432
filter => "(&(objectClass=posixAccount)(uid=$user))"
434
$mesg->code && die $mesg->error;
435
return ($mesg->count ne 0);
438
sub is_nonldap_unix_user
441
my $uid = getpwnam($user);
453
my $dn_group = shift;
455
my $mesg = $ldap->search ( base => $dn_group,
457
filter => "(&(memberUid=$user))"
459
$mesg->code && die $mesg->error;
460
return ($mesg->count ne 0);
463
# all entries = does_sid_exist($sid,$config{scope})
468
my $mesg = $ldap->search ( base => $dn_group,
469
scope => $config{scope},
470
filter => "(sambaSID=$sid)"
471
#filter => "(&(objectClass=sambaSAMAccount|objectClass=sambaGroupMapping)(sambaSID=$sid))"
473
$mesg->code && die $mesg->error;
477
# try to bind with user dn and password to validate current password
480
my ($user, $dn, $pass) = @_;
481
my $userLdap = Net::LDAP->new(
482
"$config{slaveLDAP}",
483
port => "$config{slavePort}",
487
or warn "erreur LDAP: Can't contact slave ldap server ($@)\n=>trying to contact the master server\n";
489
# connection to the slave failed: trying to contact the master ...
490
$userLdap = Net::LDAP->new(
491
"$config{masterLDAP}",
492
port => "$config{masterPort}",
496
or die "erreur LDAP: Can't contact master ldap server ($@)\n";
499
if ($config{ldapTLS} == 1) {
500
$userLdap->start_tls(
501
verify => "$config{verify}",
502
clientcert => "$config{clientcert}",
503
clientkey => "$config{clientkey}",
504
cafile => "$config{cafile}"
507
my $mesg= $userLdap->bind (dn => $dn, password => $pass );
508
if ($mesg->code eq 0) {
512
if ($userLdap->bind()) {
516
print ("The LDAP directory is not available.\n Check the server, cables ...");
520
die "Problem : contact your administrator";
526
# dn = get_dn_from_line ($dn_line)
527
# helper to get "a=b,c=d" from "dn: a=b,c=d"
536
# success = add_posix_machine($user, $uid, $gid)
537
sub add_posix_machine
539
my ($user,$uid,$gid,$wait) = @_;
540
if (!defined $wait) {
543
# bind to a directory with dn and password
544
my $add = $ldap->add ( "uid=$user,$config{computersdn}",
546
'objectclass' => ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount'],
550
'uidNumber' => "$uid",
551
'gidNumber' => "$gid",
552
'homeDirectory' => '/dev/null',
553
'loginShell' => '/bin/false',
554
'description' => 'Computer',
555
'gecos' => 'Computer',
559
$add->code && warn "failed to add entry: ", $add->error ;
565
# success = add_samba_machine_smbpasswd($computername)
566
sub add_samba_machine_smbpasswd
569
system "smbpasswd -a -m $user";
573
sub add_samba_machine
575
my ($user, $uid) = @_;
576
my $sambaSID = 2 * $uid + 1000;
580
my ($lmpassword,$ntpassword) = ntlmgen $name;
581
my $modify = $ldap->modify ( "uid=$user,$config{computersdn}",
583
replace => [objectClass => ['inetOrgPerson', 'posixAccount', 'sambaSAMAccount']],
584
add => [sambaPwdLastSet => '0'],
585
add => [sambaLogonTime => '0'],
586
add => [sambaLogoffTime => '2147483647'],
587
add => [sambaKickoffTime => '2147483647'],
588
add => [sambaPwdCanChange => '0'],
589
add => [sambaPwdMustChange => '0'],
590
add => [sambaAcctFlags => '[W ]'],
591
add => [sambaLMPassword => "$lmpassword"],
592
add => [sambaNTPassword => "$ntpassword"],
593
add => [sambaSID => "$config{SID}-$sambaSID"],
594
add => [sambaPrimaryGroupSID => "$config{SID}-0"]
598
$modify->code && die "failed to add entry: ", $modify->error ;
605
my ($group, $userid) = @_;
607
my $dn_line = get_group_dn($group);
608
if (!defined(get_group_dn($group))) {
609
print "$0: group \"$group\" doesn't exist\n";
612
if (!defined($dn_line)) {
615
my $dn = get_dn_from_line("$dn_line");
616
# on look if the user is already present in the group
617
my $is_member=is_group_member($dn,$userid);
618
if ($is_member == 1) {
619
print "User \"$userid\" already member of the group \"$group\".\n";
621
# bind to a directory with dn and password
622
# It does not matter if the user already exist, Net::LDAP will add the user
623
# if he does not exist, and ignore him if his already in the directory.
624
my $modify = $ldap->modify ( "$dn",
626
add => [memberUid => $userid]
629
$modify->code && die "failed to modify entry: ", $modify->error ;
636
# bind to a directory with dn and password
637
my $modify = $ldap->delete ($group_dn);
638
$modify->code && die "failed to delete group : ", $modify->error ;
641
sub add_grouplist_user
643
my ($grouplist, $user) = @_;
644
my @array = split(/,/, $grouplist);
645
foreach my $group (@array) {
646
group_add_user($group, $user);
654
my $dn = get_dn_from_line($dn_line);
656
if (!defined($dn_line = get_user_dn($user))) {
657
print "$0: user $user doesn't exist\n";
660
my $modify = $ldap->modify ( "$dn",
662
replace => [userPassword => '{crypt}!x']
665
$modify->code && die "failed to modify entry: ", $modify->error ;
667
if (is_samba_user($user)) {
668
my $modify = $ldap->modify ( "$dn",
670
replace => [sambaAcctFlags => '[D ]']
673
$modify->code && die "failed to modify entry: ", $modify->error ;
683
if (!defined($dn_line = get_user_dn($user))) {
684
print "$0: user $user doesn't exist\n";
688
my $dn = get_dn_from_line($dn_line);
689
my $modify = $ldap->delete($dn);
692
# $gid = group_add($groupname, $group_gid, $force_using_existing_gid)
695
my ($gname, $gid, $force) = @_;
696
my $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1";
697
if ($nscd_status == 0) {
698
system "/etc/init.d/nscd stop > /dev/null 2>&1";
700
if (!defined($gid)) {
701
#while (defined(getgrgid($config{GID_START}))) {
702
# $config{GID_START}++;
704
#$gid = $config{GID_START};
705
$gid=get_next_id($config{groupsdn},"gidNumber");
707
if (!defined($force)) {
708
if (defined(getgrgid($gid))) {
713
if ($nscd_status == 0) {
714
system "/etc/init.d/nscd start > /dev/null 2>&1";
716
my $modify = $ldap->add ( "cn=$gname,$config{groupsdn}",
718
objectClass => [ 'top', 'posixGroup' ],
724
$modify->code && die "failed to add entry: ", $modify->error ;
728
# $homedir = get_homedir ($user)
734
my $mesg = $ldap->search (
735
base =>$config{usersdn},
736
scope => $config{scope},
737
filter => "(&(objectclass=posixAccount)(uid=$user))"
739
$mesg->code && die $mesg->error;
743
print "Aborting: there are $nb existing user named $user\n";
744
foreach $entry ($mesg->all_entries) {
750
$entry = $mesg->shift_entry();
751
$homeDir= $entry->get_value("homeDirectory");
755
if ($homeDir eq '') {
766
my $mesg = $ldap->search ( # perform a search
767
base => $config{suffix},
768
scope => $config{scope},
769
filter => "(&(objectclass=posixAccount)(uid=$user))"
772
$mesg->code && die $mesg->error;
773
foreach my $entry ($mesg->all_entries) {
774
$lines.= "dn: " . $entry->dn."\n";
775
foreach my $attr ($entry->attributes) {
777
$lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
789
# return the attributes in an array
793
my $mesg = $ldap->search ( # perform a search
794
base => $config{suffix},
795
scope => $config{scope},
796
filter => "(&(objectclass=posixAccount)(uid=$user))"
799
$mesg->code && die $mesg->error;
800
my $entry = $mesg->entry();
809
my $mesg = $ldap->search ( # perform a search
810
base => $config{groupsdn},
811
scope => $config{scope},
812
filter => "(&(objectclass=posixGroup)(cn=$user))"
815
$mesg->code && die $mesg->error;
816
foreach my $entry ($mesg->all_entries) {
817
$lines.= "dn: " . $entry->dn."\n";
818
foreach my $attr ($entry->attributes) {
820
$lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
831
# find groups of a given user
832
##### MODIFIE ########
836
my $mesg = $ldap->search ( # perform a search
837
base => $config{groupsdn},
838
scope => $config{scope},
839
filter => "(&(objectclass=posixGroup)(memberuid=$user))"
841
$mesg->code && die $mesg->error;
844
while ($entry = $mesg->shift_entry()) {
845
push(@groups, scalar($entry->get_value('cn')));
850
sub read_group_entry {
854
my $mesg = $ldap->search ( # perform a search
855
base => $config{groupsdn},
856
scope => $config{scope},
857
filter => "(&(objectclass=posixGroup)(cn=$group))"
860
$mesg->code && die $mesg->error;
863
print "Error: $nb groups exist \"cn=$group\"\n";
864
foreach $entry ($mesg->all_entries) {
865
my $dn=$entry->dn; print " $dn\n";
869
$entry = $mesg->shift_entry();
874
sub read_group_entry_gid {
877
my $mesg = $ldap->search ( # perform a search
878
base => $config{groupsdn},
879
scope => $config{scope},
880
filter => "(&(objectclass=posixGroup)(gidNumber=$group))"
883
$mesg->code && die $mesg->error;
884
my $entry = $mesg->shift_entry();
888
# return the gidnumber for a group given as name or gid
889
# -1 : bad group name
893
my $userGidNumber = shift;
894
if ($userGidNumber =~ /[^\d]/ ) {
895
my $gname = $userGidNumber;
896
my $gidnum = getgrnam($gname);
897
if ($gidnum !~ /\d+/) {
900
$userGidNumber = $gidnum;
902
} elsif (!defined(getgrgid($userGidNumber))) {
905
return $userGidNumber;
908
# remove $user from $group
909
sub group_remove_member
911
my ($group, $user) = @_;
913
my $grp_line = get_group_dn($group);
914
if (!defined($grp_line)) {
917
my $dn = get_dn_from_line($grp_line);
918
# we test if the user exist in the group
919
my $is_member=is_group_member($dn,$user);
920
if ($is_member == 1) {
921
# delete only the user from the group
922
my $modify = $ldap->modify ( "$dn",
924
delete => [memberUid => ["$user"]]
927
$modify->code && die "failed to delete entry: ", $modify->error ;
932
sub group_get_members
937
my $grp_line = get_group_dn($group);
938
if (!defined($grp_line)) {
941
my $mesg = $ldap->search (
942
base => $config{groupsdn},
943
scope => $config{scope},
944
filter => "(&(objectclass=posixgroup)(cn=$group))"
946
$mesg->code && die $mesg->error;
947
foreach my $entry ($mesg->all_entries) {
948
foreach my $attr ($entry->attributes) {
949
if ($attr=~/\bmemberUid\b/) {
950
foreach my $ent ($entry->get_value($attr)) {
951
push (@resultat,$ent);
962
my $FILE = "|$config{ldapmodify} -r >/dev/null";
963
open (FILE, $FILE) || die "$!\n";
973
sub group_type_by_name {
974
my $type_name = shift;
980
return $groupmap{$type_name};
985
my ($str, $username) = @_;
986
$str =~ s/%U/$username/ if ($str);
990
# all given mails are stored in a table (remove the comma separated)
991
sub split_arg_comma {
998
@args = split(/\s*,\s*/, $arg);
1005
my ($list1, $list2) = @_;
1007
foreach my $e (@$list2) {
1008
if (! grep($_ eq $e, @$list1)) {
1016
my ($list1, $list2) = @_;
1018
foreach my $e (@$list1) {
1019
if (! grep( $_ eq $e, @$list2 )) {
1026
sub get_next_id($$) {
1027
my $ldap_base_dn = shift;
1028
my $attribute = shift;
1033
if ($ldap_base_dn =~ m/$config{usersdn}/i) {
1034
# when adding a new user, we'll check if the uidNumber available is not
1035
# already used for a computer's account
1036
$ldap_base_dn=$config{suffix}
1039
$next_uid_mesg = $ldap->search(
1040
base => $config{sambaUnixIdPooldn},
1041
filter => "(objectClass=sambaUnixIdPool)",
1044
$next_uid_mesg->code && die "Error looking for next uid";
1045
if ($next_uid_mesg->count != 1) {
1046
die "Could not find base dn, to get next $attribute";
1048
my $entry = $next_uid_mesg->entry(0);
1050
$nextuid = $entry->get_value($attribute);
1051
my $modify=$ldap->modify( "$config{sambaUnixIdPooldn}",
1053
replace => [ $attribute => $nextuid + 1 ]
1056
$modify->code && die "Error: ", $modify->error;
1057
# let's check if the id found is really free (in ou=Groups or ou=Users)...
1058
my $check_uid_mesg = $ldap->search(
1059
base => $ldap_base_dn,
1060
filter => "($attribute=$nextuid)",
1062
$check_uid_mesg->code && die "Cannot confirm $attribute $nextuid is free";
1063
if ($check_uid_mesg->count == 0) {
1068
print "Cannot confirm $attribute $nextuid is free: checking for the next one\n"
1069
} while ($found != 1);
1070
die "Could not allocate $attribute!";
1078
-charset => 'ISO-8859-1',
1087
-charset => 'ISO-8859-1',