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