1#! /usr/bin/perl -w
2use strict;
3package smbldap_tools;
4use smbldap_conf;
5use Net::LDAP;
6
7#  This code was developped by IDEALX (http://IDEALX.org/) and
8#  contributors (their names can be found in the CONTRIBUTORS file).
9#
10#                 Copyright (C) 2001-2002 IDEALX
11#
12#  This program is free software; you can redistribute it and/or
13#  modify it under the terms of the GNU General Public License
14#  as published by the Free Software Foundation; either version 2
15#  of the License, or (at your option) any later version.
16#
17#  This program is distributed in the hope that it will be useful,
18#  but WITHOUT ANY WARRANTY; without even the implied warranty of
19#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20#  GNU General Public License for more details.
21#
22#  You should have received a copy of the GNU General Public License
23#  along with this program; if not, write to the Free Software
24#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25#  USA.
26
27
28# ugly funcs using global variables and spawning openldap clients
29
30use vars       qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
31use Exporter;
32$VERSION = 1.00;
33
34@ISA = qw(Exporter);
35
36@EXPORT = qw(
37			 get_user_dn
38			 get_group_dn
39			 is_group_member
40			 is_samba_user
41			 is_unix_user
42			 is_user_valid
43			 does_sid_exist
44			 get_dn_from_line
45			 add_posix_machine
46			 add_samba_machine
47			 add_samba_machine_mkntpwd
48			 group_add_user
49			 add_grouplist_user
50			 disable_user
51			 delete_user
52			 group_add
53			 group_del
54			 get_homedir
55			 read_user
56			 read_user_entry
57			 read_group
58			 read_group_entry
59			 read_group_entry_gid
60			 find_groups_of
61			 parse_group
62			 group_remove_member
63			 group_get_members
64			 do_ldapadd
65			 do_ldapmodify
66			 get_user_dn2
67			 connect_ldap_master
68			 connect_ldap_slave
69			 group_type_by_name
70			);
71
72sub connect_ldap_master
73  {
74	# bind to a directory with dn and password
75	my $ldap_master = Net::LDAP->new(
76									 "$masterLDAP",
77									 port => "$masterPort",
78									 version => 3,
79									 # debug => 0xffff,
80									)
81	  or die "erreur LDAP: Can't contact master ldap server ($@)";
82	if ($ldapSSL == 1) {
83	  $ldap_master->start_tls(
84							  # verify => 'require',
85							  # clientcert => 'mycert.pem',
86							  # clientkey => 'mykey.pem',
87							  # decryptkey => sub { 'secret'; },
88							  # capath => '/usr/local/cacerts/'
89							 );
90	}
91	$ldap_master->bind ( "$binddn",
92						 password => "$masterPw"
93					   );
94	return($ldap_master);
95  }
96
97sub connect_ldap_slave
98  {
99	# bind to a directory with dn and password
100	my $ldap_slave = Net::LDAP->new(
101									"$slaveLDAP",
102									port => "$slavePort",
103									version => 3,
104									# debug => 0xffff,
105								   )
106	  or die "erreur LDAP: Can't contact slave ldap server ($@)";
107	if ($ldapSSL == 1) {
108	  $ldap_slave->start_tls(
109							 # verify => 'require',
110							 # clientcert => 'mycert.pem',
111							 # clientkey => 'mykey.pem',
112							 # decryptkey => sub { 'secret'; },
113							 # capath => '/usr/local/cacerts/'
114							);
115	}
116	$ldap_slave->bind ( "$binddn",
117						password => "$slavePw"
118					  );
119	return($ldap_slave);
120  }
121
122sub get_user_dn
123  {
124    my $user = shift;
125    my $dn='';
126    my $ldap_slave=connect_ldap_slave();
127    my  $mesg = $ldap_slave->search (    base   => $suffix,
128										 scope => $scope,
129										 filter => "(&(objectclass=posixAccount)(uid=$user))"
130									);
131    $mesg->code && die $mesg->error;
132    foreach my $entry ($mesg->all_entries) {
133	  $dn= $entry->dn;
134	}
135    $ldap_slave->unbind;
136    chomp($dn);
137    if ($dn eq '') {
138	  return undef;
139    }
140    $dn="dn: ".$dn;
141    return $dn;
142  }
143
144
145sub get_user_dn2
146  {
147    my $user = shift;
148    my $dn='';
149    my $ldap_slave=connect_ldap_slave();
150    my  $mesg = $ldap_slave->search (    base   => $suffix,
151										 scope => $scope,
152										 filter => "(&(objectclass=posixAccount)(uid=$user))"
153									);
154    $mesg->code && warn "failed to perform search; ", $mesg->error;
155
156    foreach my $entry ($mesg->all_entries) {
157	  $dn= $entry->dn;
158    }
159    $ldap_slave->unbind;
160    chomp($dn);
161    if ($dn eq '') {
162	  return (1,undef);
163    }
164    $dn="dn: ".$dn;
165    return (1,$dn);
166  }
167
168
169sub get_group_dn
170  {
171	my $group = shift;
172	my $dn='';
173	my $ldap_slave=connect_ldap_slave();
174	my  $mesg = $ldap_slave->search (    base   => $groupsdn,
175										 scope => $scope,
176										 filter => "(&(objectclass=posixGroup)(|(cn=$group)(gidNumber=$group)))"
177									);
178	$mesg->code && die $mesg->error;
179	foreach my $entry ($mesg->all_entries) {
180	  $dn= $entry->dn;
181	}
182	$ldap_slave->unbind;
183	chomp($dn);
184	if ($dn eq '') {
185	  return undef;
186	}
187	$dn="dn: ".$dn;
188	return $dn;
189  }
190
191# return (success, dn)
192# bool = is_samba_user($username)
193sub is_samba_user
194  {
195	my $user = shift;
196	my $ldap_slave=connect_ldap_slave();
197	my $mesg = $ldap_slave->search (    base   => $suffix,
198										scope => $scope,
199										filter => "(&(objectClass=sambaSamAccount)(uid=$user))"
200								   );
201	$mesg->code && die $mesg->error;
202	$ldap_slave->unbind;
203	return ($mesg->count ne 0);
204  }
205
206sub is_unix_user
207  {
208	my $user = shift;
209	my $ldap_slave=connect_ldap_slave();
210	my $mesg = $ldap_slave->search (    base   => $suffix,
211										scope => $scope,
212										filter => "(&(objectClass=posixAccount)(uid=$user))"
213								   );
214	$mesg->code && die $mesg->error;
215	$ldap_slave->unbind;
216	return ($mesg->count ne 0);
217  }
218
219sub is_group_member
220  {
221	my $dn_group = shift;
222	my $user = shift;
223	my $ldap_slave=connect_ldap_slave();
224	my $mesg = $ldap_slave->search (   base   => $dn_group,
225									scope => 'base',
226									filter => "(&(memberUid=$user))"
227								   );
228	$mesg->code && die $mesg->error;
229	$ldap_slave->unbind;
230	return ($mesg->count ne 0);
231  }
232
233# all entries = does_sid_exist($sid,$scope)
234sub does_sid_exist
235  {
236	my $sid = shift;
237	my $dn_group=shift;
238	my $ldap_slave=connect_ldap_slave();
239	my $mesg = $ldap_slave->search (    base   => $dn_group,
240										scope => $scope,
241										filter => "(sambaSID=$sid)"
242										#filter => "(&(objectClass=sambaSamAccount|objectClass=sambaGroupMapping)(sambaSID=$sid))"
243								   );
244	$mesg->code && die $mesg->error;
245	$ldap_slave->unbind;
246	return ($mesg);
247  }
248
249# try to bind with user dn and password to validate current password
250sub is_user_valid
251  {
252	my ($user, $dn, $pass) = @_;
253	my $ldap = Net::LDAP->new($slaveLDAP) or die "erreur LDAP";
254	my $mesg= $ldap->bind (dn => $dn, password => $pass );
255	if ($mesg->code eq 0) {
256	  $ldap->unbind;
257	  return 1;
258	} else {
259	  if ($ldap->bind()) {
260		$ldap->unbind;
261		return 0;
262	  } else {
263		print ("The LDAP directory is not available.\n Check the server, cables ...");
264		$ldap->unbind;
265		return 0;
266	  }
267	  die "Problem : contact your administrator";
268	}
269  }
270
271
272# dn = get_dn_from_line ($dn_line)
273# helper to get "a=b,c=d" from "dn: a=b,c=d"
274sub get_dn_from_line
275  {
276	my $dn = shift;
277	$dn =~ s/^dn: //;
278	return $dn;
279  }
280
281
282# success = add_posix_machine($user, $uid, $gid)
283sub add_posix_machine
284  {
285	my ($user, $uid, $gid) = @_;
286	# bind to a directory with dn and password
287	my $ldap_master=connect_ldap_master();
288	my $add = $ldap_master->add ( "uid=$user,$computersdn",
289								  attr => [
290										   'objectclass' => ['top','inetOrgPerson', 'posixAccount'],
291										   'cn'   => "$user",
292										   'sn'   => "$user",
293										   'uid'   => "$user",
294										   'uidNumber'   => "$uid",
295										   'gidNumber'   => "$gid",
296										   'homeDirectory'   => '/dev/null',
297										   'loginShell'   => '/bin/false',
298										   'description'   => 'Computer',
299										  ]
300								);
301
302	$add->code && warn "failed to add entry: ", $add->error ;
303	# take down the session
304	$ldap_master->unbind;
305
306  }
307
308
309# success = add_samba_machine($computername)
310sub add_samba_machine
311  {
312    my $user = shift;
313    system "smbpasswd -a -m $user";
314    return 1;
315  }
316
317sub add_samba_machine_mkntpwd
318  {
319	my ($user, $uid) = @_;
320	my $sambaSID = 2 * $uid + 1000;
321	my $name = $user;
322	$name =~ s/.$//s;
323
324	if ($mk_ntpasswd eq '') {
325	  print "Either set \$with_smbpasswd = 1 or specify \$mk_ntpasswd\n";
326	  return 0;
327	}
328
329	my $ntpwd = `$mk_ntpasswd '$name'`;
330	chomp(my $lmpassword = substr($ntpwd, 0, index($ntpwd, ':')));
331	chomp(my $ntpassword = substr($ntpwd, index($ntpwd, ':')+1));
332
333	my $ldap_master=connect_ldap_master();
334	my $modify = $ldap_master->modify ( "uid=$user,$computersdn",
335										changes => [
336													replace => [objectClass => ['inetOrgPerson', 'posixAccount', 'sambaSamAccount']],
337													add => [sambaPwdLastSet => '0'],
338													add => [sambaLogonTime => '0'],
339													add => [sambaLogoffTime => '2147483647'],
340													add => [sambaKickoffTime => '2147483647'],
341													add => [sambaPwdCanChange => '0'],
342													add => [sambaPwdMustChange => '0'],
343													add => [sambaAcctFlags => '[W          ]'],
344													add => [sambaLMPassword => "$lmpassword"],
345													add => [sambaNTPassword => "$ntpassword"],
346													add => [sambaSID => "$SID-$sambaSID"],
347													add => [sambaPrimaryGroupSID => "$SID-0"]
348												   ]
349									  );
350
351	$modify->code && die "failed to add entry: ", $modify->error ;
352
353	return 1;
354	# take down the session
355	$ldap_master->unbind;
356
357  }
358
359
360sub group_add_user
361  {
362	my ($group, $userid) = @_;
363	my $members='';
364	my $dn_line = get_group_dn($group);
365	if (!defined(get_group_dn($group))) {
366	  print "$0: group \"$group\" doesn't exist\n";
367	  exit (6);
368	}
369	if (!defined($dn_line)) {
370	  return 1;
371	}
372	my $dn = get_dn_from_line("$dn_line");
373	# on look if the user is already present in the group
374	my $is_member=is_group_member($dn,$userid);
375	if ($is_member == 1) {
376	  print "User \"$userid\" already member of the group \"$group\".\n";
377	} else {
378	  # bind to a directory with dn and password
379	  my $ldap_master=connect_ldap_master();
380	  # It does not matter if the user already exist, Net::LDAP will add the user
381	  # if he does not exist, and ignore him if his already in the directory.
382	  my $modify = $ldap_master->modify ( "$dn",
383										  changes => [
384													  add => [memberUid => $userid]
385													 ]
386										);
387	  $modify->code && die "failed to modify entry: ", $modify->error ;
388	  # take down session
389	  $ldap_master->unbind;
390	}
391  }
392
393sub group_del
394  {
395	my $group_dn=shift;
396	# bind to a directory with dn and password
397	my $ldap_master=connect_ldap_master();
398	my $modify = $ldap_master->delete ($group_dn);
399	$modify->code && die "failed to delete group : ", $modify->error ;
400	# take down session
401	$ldap_master->unbind;
402  }
403
404sub add_grouplist_user
405  {
406	my ($grouplist, $user) = @_;
407	my @array = split(/,/, $grouplist);
408	foreach my $group (@array) {
409	  group_add_user($group, $user);
410	}
411  }
412
413sub disable_user
414  {
415	my $user = shift;
416	my $dn_line;
417	my $dn = get_dn_from_line($dn_line);
418
419	if (!defined($dn_line = get_user_dn($user))) {
420	  print "$0: user $user doesn't exist\n";
421	  exit (10);
422	}
423	my $ldap_master=connect_ldap_master();
424	my $modify = $ldap_master->modify ( "$dn",
425										changes => [
426													replace => [userPassword => '{crypt}!x']
427												   ]
428									  );
429	$modify->code && die "failed to modify entry: ", $modify->error ;
430
431	if (is_samba_user($user)) {
432	  my $modify = $ldap_master->modify ( "$dn",
433										  changes => [
434													  replace => [sambaAcctFlags => '[D       ]']
435													 ]
436										);
437	  $modify->code && die "failed to modify entry: ", $modify->error ;
438	}
439	# take down session
440	$ldap_master->unbind;
441  }
442
443# delete_user($user)
444sub delete_user
445  {
446	my $user = shift;
447	my $dn_line;
448
449	if (!defined($dn_line = get_user_dn($user))) {
450	  print "$0: user $user doesn't exist\n";
451	  exit (10);
452	}
453
454	my $dn = get_dn_from_line($dn_line);
455	my $ldap_master=connect_ldap_master();
456	my $modify = $ldap_master->delete($dn);
457	$ldap_master->unbind;
458  }
459
460# $gid = group_add($groupname, $group_gid, $force_using_existing_gid)
461sub group_add
462  {
463	my ($gname, $gid, $force) = @_;
464	my $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1";
465	if ($nscd_status == 0) {
466	  system "/etc/init.d/nscd stop > /dev/null 2>&1";
467	}
468	if (!defined($gid)) {
469	  while (defined(getgrgid($GID_START))) {
470		$GID_START++;
471	  }
472	  $gid = $GID_START;
473	} else {
474	  if (!defined($force)) {
475		if (defined(getgrgid($gid))) {
476		  return undef;
477		}
478	  }
479	}
480	if ($nscd_status == 0) {
481	  system "/etc/init.d/nscd start > /dev/null 2>&1";
482	}
483	my $ldap_master=connect_ldap_master();
484	my $modify = $ldap_master->add ( "cn=$gname,$groupsdn",
485									 attrs => [
486											   objectClass => 'posixGroup',
487											   cn => "$gname",
488											   gidNumber => "$gid"
489											  ]
490								   );
491
492	$modify->code && die "failed to add entry: ", $modify->error ;
493	# take down session
494	$ldap_master->unbind;
495	return $gid;
496  }
497
498# $homedir = get_homedir ($user)
499sub get_homedir
500  {
501	my $user = shift;
502	my $homeDir='';
503	my $ldap_slave=connect_ldap_slave();
504	my  $mesg = $ldap_slave->search (
505									 base   =>$suffix,
506									 scope => $scope,
507									 filter => "(&(objectclass=posixAccount)(uid=$user))"
508									);
509	$mesg->code && die $mesg->error;
510	foreach my $entry ($mesg->all_entries) {
511	  foreach my $attr ($entry->attributes) {
512		if ($attr=~/\bhomeDirectory\b/) {
513		  foreach my $ent ($entry->get_value($attr)) {
514			$homeDir.= $attr.": ".$ent."\n";
515		  }
516		}
517	  }
518	}
519	$ldap_slave->unbind;
520	chomp $homeDir;
521	if ($homeDir eq '') {
522	  return undef;
523	}
524	$homeDir =~ s/^homeDirectory: //;
525	return $homeDir;
526  }
527
528# search for an user
529sub read_user
530  {
531	my $user = shift;
532	my $lines ='';
533	my $ldap_slave=connect_ldap_slave();
534	my $mesg = $ldap_slave->search ( # perform a search
535									base   => $suffix,
536									scope => $scope,
537									filter => "(&(objectclass=posixAccount)(uid=$user))"
538								   );
539
540	$mesg->code && die $mesg->error;
541	foreach my $entry ($mesg->all_entries) {
542	  $lines.= "dn: " . $entry->dn."\n";
543	  foreach my $attr ($entry->attributes) {
544		{
545		  $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
546		}
547	  }
548	}
549	# take down session
550	$ldap_slave->unbind;
551	chomp $lines;
552	if ($lines eq '') {
553	  return undef;
554	}
555	return $lines;
556  }
557
558# search for a user
559# return the attributes in an array
560sub read_user_entry
561  {
562	my $user = shift;
563	my $ldap_slave=connect_ldap_slave();
564	my  $mesg = $ldap_slave->search ( # perform a search
565									 base   => $suffix,
566									 scope => $scope,
567									 filter => "(&(objectclass=posixAccount)(uid=$user))"
568									);
569
570	$mesg->code && die $mesg->error;
571	my $entry = $mesg->entry();
572	$ldap_slave->unbind;
573	return $entry;
574  }
575
576# search for a group
577sub read_group
578  {
579	my $user = shift;
580	my $lines ='';
581	my $ldap_slave=connect_ldap_slave();
582	my  $mesg = $ldap_slave->search ( # perform a search
583									 base   => $groupsdn,
584									 scope => $scope,
585									 filter => "(&(objectclass=posixGroup)(cn=$user))"
586									);
587
588	$mesg->code && die $mesg->error;
589	foreach my $entry ($mesg->all_entries) {
590	  $lines.= "dn: " . $entry->dn."\n";
591	  foreach my $attr ($entry->attributes) {
592		{
593		  $lines.= $attr.": ".join(',', $entry->get_value($attr))."\n";
594		}
595	  }
596	}
597	# take down session
598	$ldap_slave->unbind;
599	chomp $lines;
600	if ($lines eq '') {
601	  return undef;
602	}
603	return $lines;
604  }
605
606# find groups of a given user
607##### MODIFIE ########
608sub find_groups_of
609  {
610	my $user = shift;
611	my $lines ='';
612	my $ldap_slave=connect_ldap_slave;
613	my  $mesg = $ldap_slave->search ( # perform a search
614									 base   => $groupsdn,
615									 scope => $scope,
616									 filter => "(&(objectclass=posixGroup)(memberuid=$user))"
617									);
618	$mesg->code && die $mesg->error;
619	foreach my $entry ($mesg->all_entries) {
620	  $lines.= "dn: ".$entry->dn."\n";
621	}
622	$ldap_slave->unbind;
623	chomp($lines);
624	if ($lines eq '') {
625	  return undef;
626	}
627	return $lines;
628  }
629
630sub read_group_entry {
631  my $group = shift;
632  my $entry;
633  my %res;
634  my $ldap_slave=connect_ldap_slave();
635  my  $mesg = $ldap_slave->search ( # perform a search
636								   base   => $groupsdn,
637								   scope => $scope,
638								   filter => "(&(objectclass=posixGroup)(cn=$group))"
639								  );
640
641  $mesg->code && die $mesg->error;
642  my $nb=$mesg->count;
643  if ($nb > 1) {
644    print "Error: $nb groups exist \"cn=$group\"\n";
645    foreach $entry ($mesg->all_entries) { my $dn=$entry->dn; print "  $dn\n"; }
646    exit 11;
647  } else {
648    $entry = $mesg->shift_entry();
649  }
650  return $entry;
651}
652
653sub read_group_entry_gid {
654  my $group = shift;
655  my %res;
656  my $ldap_slave=connect_ldap_slave();
657  my  $mesg = $ldap_slave->search ( # perform a search
658                                  base   => $groupsdn,
659                                  scope => $scope,
660                                  filter => "(&(objectclass=posixGroup)(gidNumber=$group))"
661                                 );
662
663  $mesg->code && die $mesg->error;
664  my $entry = $mesg->shift_entry();
665  return $entry;
666}
667
668# return the gidnumber for a group given as name or gid
669# -1 : bad group name
670# -2 : bad gidnumber
671sub parse_group
672  {
673	my $userGidNumber = shift;
674	if ($userGidNumber =~ /[^\d]/ ) {
675	  my $gname = $userGidNumber;
676	  my $gidnum = getgrnam($gname);
677	  if ($gidnum !~ /\d+/) {
678		return -1;
679	  } else {
680		$userGidNumber = $gidnum;
681	  }
682	} elsif (!defined(getgrgid($userGidNumber))) {
683	  return -2;
684	}
685	return $userGidNumber;
686  }
687
688# remove $user from $group
689sub group_remove_member
690  {
691	my ($group, $user) = @_;
692	my $members='';
693	my $grp_line = get_group_dn($group);
694	if (!defined($grp_line)) {
695	  return 0;
696	}
697	my $dn = get_dn_from_line($grp_line);
698	# we test if the user exist in the group
699	my $is_member=is_group_member($dn,$user);
700	if ($is_member == 1) {
701	  my $ldap_master=connect_ldap_master();
702	  # delete only the user from the group
703	  my $modify = $ldap_master->modify ( "$dn",
704										  changes => [
705													  delete => [memberUid => ["$user"]]
706													 ]
707										);
708	  $modify->code && die "failed to delete entry: ", $modify->error ;
709	  $ldap_master->unbind;
710	}
711	return 1;
712  }
713
714sub group_get_members
715  {
716	my ($group) = @_;
717	my $members;
718	my @resultat;
719	my $grp_line = get_group_dn($group);
720	if (!defined($grp_line)) {
721	  return 0;
722	}
723
724	my $ldap = Net::LDAP->new($slaveLDAP) or die "erreur LDAP";
725	$ldap->bind ;
726	my  $mesg = $ldap->search (
727							   base   => $groupsdn,
728							   scope => $scope,
729							   filter => "(&(objectclass=posixgroup)(cn=$group))"
730							  );
731	$mesg->code && die $mesg->error;
732	foreach my $entry ($mesg->all_entries) {
733	  foreach my $attr ($entry->attributes) {
734		if ($attr=~/\bmemberUid\b/) {
735		  foreach my $ent ($entry->get_value($attr)) {
736			push (@resultat,$ent);
737		  }
738		}
739	  }
740	}
741	return @resultat;
742  }
743
744sub do_ldapmodify
745  {
746      my $ldif = shift;
747      my $FILE = "|$ldapmodify -r >/dev/null";
748      open (FILE, $FILE) || die "$!\n";
749      print FILE <<EOF;
750$ldif
751EOF
752      ;
753      close FILE;
754      my $rc = $?;
755      return $rc;
756  }
757
758sub group_type_by_name {
759  my $type_name = shift;
760  my %groupmap = (
761    'domain' => 2,
762    'local' => 4,
763    'builtin' => 5
764  );
765  return $groupmap{$type_name};
766}
767
768
769
7701;
771
772