[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY 4 * Version 3.3.2 5 * 6 * PHP Version 5 with SSL and LDAP support 7 * 8 * Written by Scott Barnett, Richard Hyland 9 * email: scott@wiggumworld.com, adldap@richardhyland.com 10 * http://adldap.sourceforge.net/ 11 * 12 * Copyright (c) 2006-2010 Scott Barnett, Richard Hyland 13 * 14 * We'd appreciate any improvements or additions to be submitted back 15 * to benefit the entire community :) 16 * 17 * This library is free software; you can redistribute it and/or 18 * modify it under the terms of the GNU Lesser General Public 19 * License as published by the Free Software Foundation; either 20 * version 2.1 of the License. 21 * 22 * This library is distributed in the hope that it will be useful, 23 * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25 * Lesser General Public License for more details. 26 * 27 * @category ToolsAndUtilities 28 * @package adLDAP 29 * @author Scott Barnett, Richard Hyland 30 * @copyright (c) 2006-2010 Scott Barnett, Richard Hyland 31 * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1 32 * @revision $Revision: 91 $ 33 * @version 3.3.2 34 * @link http://adldap.sourceforge.net/ 35 */ 36 37 /** 38 * Define the different types of account in AD 39 */ 40 define ('ADLDAP_NORMAL_ACCOUNT', 805306368); 41 define ('ADLDAP_WORKSTATION_TRUST', 805306369); 42 define ('ADLDAP_INTERDOMAIN_TRUST', 805306370); 43 define ('ADLDAP_SECURITY_GLOBAL_GROUP', 268435456); 44 define ('ADLDAP_DISTRIBUTION_GROUP', 268435457); 45 define ('ADLDAP_SECURITY_LOCAL_GROUP', 536870912); 46 define ('ADLDAP_DISTRIBUTION_LOCAL_GROUP', 536870913); 47 define ('ADLDAP_FOLDER', 'OU'); 48 define ('ADLDAP_CONTAINER', 'CN'); 49 50 /** 51 * Main adLDAP class 52 * 53 * Can be initialised using $adldap = new adLDAP(); 54 * 55 * Something to keep in mind is that Active Directory is a permissions 56 * based directory. If you bind as a domain user, you can't fetch as 57 * much information on other users as you could as a domain admin. 58 * 59 * Before asking questions, please read the Documentation at 60 * http://adldap.sourceforge.net/wiki/doku.php?id=api 61 */ 62 class adLDAP { 63 /** 64 * The account suffix for your domain, can be set when the class is invoked 65 * 66 * @var string 67 */ 68 protected $_account_suffix = "@mydomain.local"; 69 70 /** 71 * The base dn for your domain 72 * 73 * @var string 74 */ 75 protected $_base_dn = "DC=mydomain,DC=local"; 76 77 /** 78 * Array of domain controllers. Specifiy multiple controllers if you 79 * would like the class to balance the LDAP queries amongst multiple servers 80 * 81 * @var array 82 */ 83 protected $_domain_controllers = array ("dc01.mydomain.local"); 84 85 /** 86 * Optional account with higher privileges for searching 87 * This should be set to a domain admin account 88 * 89 * @var string 90 * @var string 91 */ 92 protected $_ad_username=NULL; 93 protected $_ad_password=NULL; 94 95 /** 96 * AD does not return the primary group. http://support.microsoft.com/?kbid=321360 97 * This tweak will resolve the real primary group. 98 * Setting to false will fudge "Domain Users" and is much faster. Keep in mind though that if 99 * someone's primary group is NOT domain users, this is obviously going to mess up the results 100 * 101 * @var bool 102 */ 103 protected $_real_primarygroup=true; 104 105 /** 106 * Use SSL (LDAPS), your server needs to be setup, please see 107 * http://adldap.sourceforge.net/wiki/doku.php?id=ldap_over_ssl 108 * 109 * @var bool 110 */ 111 protected $_use_ssl=false; 112 113 /** 114 * Use TLS 115 * If you wish to use TLS you should ensure that $_use_ssl is set to false and vice-versa 116 * 117 * @var bool 118 */ 119 protected $_use_tls=false; 120 121 /** 122 * When querying group memberships, do it recursively 123 * eg. User Fred is a member of Group A, which is a member of Group B, which is a member of Group C 124 * user_ingroup("Fred","C") will returns true with this option turned on, false if turned off 125 * 126 * @var bool 127 */ 128 protected $_recursive_groups=true; 129 130 // You should not need to edit anything below this line 131 //****************************************************************************************** 132 133 /** 134 * Connection and bind default variables 135 * 136 * @var mixed 137 * @var mixed 138 */ 139 protected $_conn; 140 protected $_bind; 141 142 /** 143 * Getters and Setters 144 */ 145 146 /** 147 * Set the account suffix 148 * 149 * @param string $_account_suffix 150 * @return void 151 */ 152 public function set_account_suffix($_account_suffix) 153 { 154 $this->_account_suffix = $_account_suffix; 155 } 156 157 /** 158 * Get the account suffix 159 * 160 * @return string 161 */ 162 public function get_account_suffix() 163 { 164 return $this->_account_suffix; 165 } 166 167 /** 168 * Set the domain controllers array 169 * 170 * @param array $_domain_controllers 171 * @return void 172 */ 173 public function set_domain_controllers(array $_domain_controllers) 174 { 175 $this->_domain_controllers = $_domain_controllers; 176 } 177 178 /** 179 * Get the list of domain controllers 180 * 181 * @return void 182 */ 183 public function get_domain_controllers() 184 { 185 return $this->_domain_controllers; 186 } 187 188 /** 189 * Set the username of an account with higher priviledges 190 * 191 * @param string $_ad_username 192 * @return void 193 */ 194 public function set_ad_username($_ad_username) 195 { 196 $this->_ad_username = $_ad_username; 197 } 198 199 /** 200 * Get the username of the account with higher priviledges 201 * 202 * This will throw an exception for security reasons 203 */ 204 public function get_ad_username() 205 { 206 throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); 207 } 208 209 /** 210 * Set the password of an account with higher priviledges 211 * 212 * @param string $_ad_password 213 * @return void 214 */ 215 public function set_ad_password($_ad_password) 216 { 217 $this->_ad_password = $_ad_password; 218 } 219 220 /** 221 * Get the password of the account with higher priviledges 222 * 223 * This will throw an exception for security reasons 224 */ 225 public function get_ad_password() 226 { 227 throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); 228 } 229 230 /** 231 * Set whether to detect the true primary group 232 * 233 * @param bool $_real_primary_group 234 * @return void 235 */ 236 public function set_real_primarygroup($_real_primarygroup) 237 { 238 $this->_real_primarygroup = $_real_primarygroup; 239 } 240 241 /** 242 * Get the real primary group setting 243 * 244 * @return bool 245 */ 246 public function get_real_primarygroup() 247 { 248 return $this->_real_primarygroup; 249 } 250 251 /** 252 * Set whether to use SSL 253 * 254 * @param bool $_use_ssl 255 * @return void 256 */ 257 public function set_use_ssl($_use_ssl) 258 { 259 $this->_use_ssl = $_use_ssl; 260 } 261 262 /** 263 * Get the SSL setting 264 * 265 * @return bool 266 */ 267 public function get_use_ssl() 268 { 269 return $this->_use_ssl; 270 } 271 272 /** 273 * Set whether to use TLS 274 * 275 * @param bool $_use_tls 276 * @return void 277 */ 278 public function set_use_tls($_use_tls) 279 { 280 $this->_use_tls = $_use_tls; 281 } 282 283 /** 284 * Get the TLS setting 285 * 286 * @return bool 287 */ 288 public function get_use_tls() 289 { 290 return $this->_use_tls; 291 } 292 293 /** 294 * Set whether to lookup recursive groups 295 * 296 * @param bool $_recursive_groups 297 * @return void 298 */ 299 public function set_recursive_groups($_recursive_groups) 300 { 301 $this->_recursive_groups = $_recursive_groups; 302 } 303 304 /** 305 * Get the recursive groups setting 306 * 307 * @return bool 308 */ 309 public function get_recursive_groups() 310 { 311 return $this->_recursive_groups; 312 } 313 314 /** 315 * Default Constructor 316 * 317 * Tries to bind to the AD domain over LDAP or LDAPs 318 * 319 * @param array $options Array of options to pass to the constructor 320 * @throws Exception - if unable to bind to Domain Controller 321 * @return bool 322 */ 323 function __construct($options=array()){ 324 // You can specifically overide any of the default configuration options setup above 325 if (count($options)>0){ 326 if (array_key_exists("account_suffix",$options)){ $this->_account_suffix=$options["account_suffix"]; } 327 if (array_key_exists("base_dn",$options)){ $this->_base_dn=$options["base_dn"]; } 328 if (array_key_exists("domain_controllers",$options)){ $this->_domain_controllers=$options["domain_controllers"]; } 329 if (array_key_exists("ad_username",$options)){ $this->_ad_username=$options["ad_username"]; } 330 if (array_key_exists("ad_password",$options)){ $this->_ad_password=$options["ad_password"]; } 331 if (array_key_exists("real_primarygroup",$options)){ $this->_real_primarygroup=$options["real_primarygroup"]; } 332 if (array_key_exists("use_ssl",$options)){ $this->_use_ssl=$options["use_ssl"]; } 333 if (array_key_exists("use_tls",$options)){ $this->_use_tls=$options["use_tls"]; } 334 if (array_key_exists("recursive_groups",$options)){ $this->_recursive_groups=$options["recursive_groups"]; } 335 } 336 337 if ($this->ldap_supported() === false) { 338 throw new adLDAPException('No LDAP support for PHP. See: http://www.php.net/ldap'); 339 } 340 341 return $this->connect(); 342 } 343 344 /** 345 * Default Destructor 346 * 347 * Closes the LDAP connection 348 * 349 * @return void 350 */ 351 function __destruct(){ $this->close(); } 352 353 /** 354 * Connects and Binds to the Domain Controller 355 * 356 * @return bool 357 */ 358 public function connect() { 359 // Connect to the AD/LDAP server as the username/password 360 $dc=$this->random_controller(); 361 if ($this->_use_ssl){ 362 $this->_conn = ldap_connect("ldaps://".$dc, 636); 363 } else { 364 $this->_conn = ldap_connect($dc); 365 } 366 367 // Set some ldap options for talking to AD 368 ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3); 369 ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 0); 370 371 if ($this->_use_tls) { 372 ldap_start_tls($this->_conn); 373 } 374 375 // Bind as a domain admin if they've set it up 376 if ($this->_ad_username!=NULL && $this->_ad_password!=NULL){ 377 $this->_bind = @ldap_bind($this->_conn,$this->_ad_username.$this->_account_suffix,$this->_ad_password); 378 if (!$this->_bind){ 379 if ($this->_use_ssl && !$this->_use_tls){ 380 // If you have problems troubleshooting, remove the @ character from the ldap_bind command above to get the actual error message 381 throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->get_last_error()); 382 } else { 383 throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->get_last_error()); 384 } 385 } 386 } 387 388 if ($this->_base_dn == NULL) { 389 $this->_base_dn = $this->find_base_dn(); 390 } 391 392 return (true); 393 } 394 395 /** 396 * Closes the LDAP connection 397 * 398 * @return void 399 */ 400 public function close() { 401 ldap_close ($this->_conn); 402 } 403 404 /** 405 * Validate a user's login credentials 406 * 407 * @param string $username A user's AD username 408 * @param string $password A user's AD password 409 * @param bool optional $prevent_rebind 410 * @return bool 411 */ 412 public function authenticate($username, $password, $prevent_rebind = false) { 413 // Prevent null binding 414 if ($username === NULL || $password === NULL) { return false; } 415 if (empty($username) || empty($password)) { return false; } 416 417 // Bind as the user 418 $ret = true; 419 $this->_bind = @ldap_bind($this->_conn, $username . $this->_account_suffix, $password); 420 if (!$this->_bind){ $ret = false; } 421 422 // Cnce we've checked their details, kick back into admin mode if we have it 423 if ($this->_ad_username !== NULL && !$prevent_rebind) { 424 $this->_bind = @ldap_bind($this->_conn, $this->_ad_username . $this->_account_suffix , $this->_ad_password); 425 if (!$this->_bind){ 426 // This should never happen in theory 427 throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->get_last_error()); 428 } 429 } 430 431 return $ret; 432 } 433 434 //***************************************************************************************************************** 435 // GROUP FUNCTIONS 436 437 /** 438 * Add a group to a group 439 * 440 * @param string $parent The parent group name 441 * @param string $child The child group name 442 * @return bool 443 */ 444 public function group_add_group($parent,$child){ 445 446 // Find the parent group's dn 447 $parent_group=$this->group_info($parent,array("cn")); 448 if ($parent_group[0]["dn"]===NULL){ return (false); } 449 $parent_dn=$parent_group[0]["dn"]; 450 451 // Find the child group's dn 452 $child_group=$this->group_info($child,array("cn")); 453 if ($child_group[0]["dn"]===NULL){ return (false); } 454 $child_dn=$child_group[0]["dn"]; 455 456 $add=array(); 457 $add["member"] = $child_dn; 458 459 $result=@ldap_mod_add($this->_conn,$parent_dn,$add); 460 if ($result==false){ return (false); } 461 return (true); 462 } 463 464 /** 465 * Add a user to a group 466 * 467 * @param string $group The group to add the user to 468 * @param string $user The user to add to the group 469 * @param bool $isGUID Is the username passed a GUID or a samAccountName 470 * @return bool 471 */ 472 public function group_add_user($group,$user,$isGUID=false){ 473 // Adding a user is a bit fiddly, we need to get the full DN of the user 474 // and add it using the full DN of the group 475 476 // Find the user's dn 477 $user_dn=$this->user_dn($user,$isGUID); 478 if ($user_dn===false){ return (false); } 479 480 // Find the group's dn 481 $group_info=$this->group_info($group,array("cn")); 482 if ($group_info[0]["dn"]===NULL){ return (false); } 483 $group_dn=$group_info[0]["dn"]; 484 485 $add=array(); 486 $add["member"] = $user_dn; 487 488 $result=@ldap_mod_add($this->_conn,$group_dn,$add); 489 if ($result==false){ return (false); } 490 return (true); 491 } 492 493 /** 494 * Add a contact to a group 495 * 496 * @param string $group The group to add the contact to 497 * @param string $contact_dn The DN of the contact to add 498 * @return bool 499 */ 500 public function group_add_contact($group,$contact_dn){ 501 // To add a contact we take the contact's DN 502 // and add it using the full DN of the group 503 504 // Find the group's dn 505 $group_info=$this->group_info($group,array("cn")); 506 if ($group_info[0]["dn"]===NULL){ return (false); } 507 $group_dn=$group_info[0]["dn"]; 508 509 $add=array(); 510 $add["member"] = $contact_dn; 511 512 $result=@ldap_mod_add($this->_conn,$group_dn,$add); 513 if ($result==false){ return (false); } 514 return (true); 515 } 516 517 /** 518 * Create a group 519 * 520 * @param array $attributes Default attributes of the group 521 * @return bool 522 */ 523 public function group_create($attributes){ 524 if (!is_array($attributes)){ return ("Attributes must be an array"); } 525 if (!array_key_exists("group_name",$attributes)){ return ("Missing compulsory field [group_name]"); } 526 if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } 527 if (!array_key_exists("description",$attributes)){ return ("Missing compulsory field [description]"); } 528 if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } 529 $attributes["container"]=array_reverse($attributes["container"]); 530 531 //$member_array = array(); 532 //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; 533 //$member_array[1] = "cn=administrator,cn=Users,dc=yourdomain,dc=com"; 534 535 $add=array(); 536 $add["cn"] = $attributes["group_name"]; 537 $add["samaccountname"] = $attributes["group_name"]; 538 $add["objectClass"] = "Group"; 539 $add["description"] = $attributes["description"]; 540 //$add["member"] = $member_array; UNTESTED 541 542 $container="OU=".implode(",OU=",$attributes["container"]); 543 $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add); 544 if ($result!=true){ return (false); } 545 546 return (true); 547 } 548 549 /** 550 * Remove a group from a group 551 * 552 * @param string $parent The parent group name 553 * @param string $child The child group name 554 * @return bool 555 */ 556 public function group_del_group($parent,$child){ 557 558 // Find the parent dn 559 $parent_group=$this->group_info($parent,array("cn")); 560 if ($parent_group[0]["dn"]===NULL){ return (false); } 561 $parent_dn=$parent_group[0]["dn"]; 562 563 // Find the child dn 564 $child_group=$this->group_info($child,array("cn")); 565 if ($child_group[0]["dn"]===NULL){ return (false); } 566 $child_dn=$child_group[0]["dn"]; 567 568 $del=array(); 569 $del["member"] = $child_dn; 570 571 $result=@ldap_mod_del($this->_conn,$parent_dn,$del); 572 if ($result==false){ return (false); } 573 return (true); 574 } 575 576 /** 577 * Remove a user from a group 578 * 579 * @param string $group The group to remove a user from 580 * @param string $user The AD user to remove from the group 581 * @param bool $isGUID Is the username passed a GUID or a samAccountName 582 * @return bool 583 */ 584 public function group_del_user($group,$user,$isGUID=false){ 585 586 // Find the parent dn 587 $group_info=$this->group_info($group,array("cn")); 588 if ($group_info[0]["dn"]===NULL){ return (false); } 589 $group_dn=$group_info[0]["dn"]; 590 591 // Find the users dn 592 $user_dn=$this->user_dn($user,$isGUID); 593 if ($user_dn===false){ return (false); } 594 595 $del=array(); 596 $del["member"] = $user_dn; 597 598 $result=@ldap_mod_del($this->_conn,$group_dn,$del); 599 if ($result==false){ return (false); } 600 return (true); 601 } 602 603 /** 604 * Remove a contact from a group 605 * 606 * @param string $group The group to remove a user from 607 * @param string $contact_dn The DN of a contact to remove from the group 608 * @return bool 609 */ 610 public function group_del_contact($group,$contact_dn){ 611 612 // Find the parent dn 613 $group_info=$this->group_info($group,array("cn")); 614 if ($group_info[0]["dn"]===NULL){ return (false); } 615 $group_dn=$group_info[0]["dn"]; 616 617 $del=array(); 618 $del["member"] = $contact_dn; 619 620 $result=@ldap_mod_del($this->_conn,$group_dn,$del); 621 if ($result==false){ return (false); } 622 return (true); 623 } 624 625 /** 626 * Return a list of groups in a group 627 * 628 * @param string $group The group to query 629 * @param bool $recursive Recursively get groups 630 * @return array 631 */ 632 public function groups_in_group($group, $recursive = NULL){ 633 if (!$this->_bind){ return (false); } 634 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it 635 636 // Search the directory for the members of a group 637 $info=$this->group_info($group,array("member","cn")); 638 $groups=$info[0]["member"]; 639 if (!is_array($groups)) { 640 return (false); 641 } 642 643 $group_array=array(); 644 645 for ($i=0; $i<$groups["count"]; $i++){ 646 $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($groups[$i])."))"; 647 $fields = array("samaccountname", "distinguishedname", "objectClass"); 648 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 649 $entries = ldap_get_entries($this->_conn, $sr); 650 651 // not a person, look for a group 652 if ($entries['count'] == 0 && $recursive == true) { 653 $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($groups[$i])."))"; 654 $fields = array("distinguishedname"); 655 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 656 $entries = ldap_get_entries($this->_conn, $sr); 657 if (!isset($entries[0]['distinguishedname'][0])) { 658 continue; 659 } 660 $sub_groups = $this->groups_in_group($entries[0]['distinguishedname'][0], $recursive); 661 if (is_array($sub_groups)) { 662 $group_array = array_merge($group_array, $sub_groups); 663 $group_array = array_unique($group_array); 664 } 665 continue; 666 } 667 668 $group_array[] = $entries[0]['distinguishedname'][0]; 669 } 670 return ($group_array); 671 } 672 673 /** 674 * Return a list of members in a group 675 * 676 * @param string $group The group to query 677 * @param bool $recursive Recursively get group members 678 * @return array 679 */ 680 public function group_members($group, $recursive = NULL){ 681 if (!$this->_bind){ return (false); } 682 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it 683 // Search the directory for the members of a group 684 $info=$this->group_info($group,array("member","cn")); 685 $users=$info[0]["member"]; 686 if (!is_array($users)) { 687 return (false); 688 } 689 690 $user_array=array(); 691 692 for ($i=0; $i<$users["count"]; $i++){ 693 $filter="(&(objectCategory=person)(distinguishedName=".$this->ldap_slashes($users[$i])."))"; 694 $fields = array("samaccountname", "distinguishedname", "objectClass"); 695 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 696 $entries = ldap_get_entries($this->_conn, $sr); 697 698 // not a person, look for a group 699 if ($entries['count'] == 0 && $recursive == true) { 700 $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($users[$i])."))"; 701 $fields = array("samaccountname"); 702 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 703 $entries = ldap_get_entries($this->_conn, $sr); 704 if (!isset($entries[0]['samaccountname'][0])) { 705 continue; 706 } 707 $sub_users = $this->group_members($entries[0]['samaccountname'][0], $recursive); 708 if (is_array($sub_users)) { 709 $user_array = array_merge($user_array, $sub_users); 710 $user_array = array_unique($user_array); 711 } 712 continue; 713 } 714 715 if ($entries[0]['samaccountname'][0] === NULL && $entries[0]['distinguishedname'][0] !== NULL) { 716 $user_array[] = $entries[0]['distinguishedname'][0]; 717 } 718 elseif ($entries[0]['samaccountname'][0] !== NULL) { 719 $user_array[] = $entries[0]['samaccountname'][0]; 720 } 721 } 722 return ($user_array); 723 } 724 725 /** 726 * Group Information. Returns an array of information about a group. 727 * The group name is case sensitive 728 * 729 * @param string $group_name The group name to retrieve info about 730 * @param array $fields Fields to retrieve 731 * @return array 732 */ 733 public function group_info($group_name,$fields=NULL){ 734 if ($group_name===NULL){ return (false); } 735 if (!$this->_bind){ return (false); } 736 737 if (stristr($group_name, '+')) { 738 $group_name=stripslashes($group_name); 739 } 740 741 $filter="(&(objectCategory=group)(name=".$this->ldap_slashes($group_name)."))"; 742 //echo ($filter."!!!<br>"); 743 if ($fields===NULL){ $fields=array("member","memberof","cn","description","distinguishedname","objectcategory","samaccountname"); } 744 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 745 $entries = ldap_get_entries($this->_conn, $sr); 746 //print_r($entries); 747 return ($entries); 748 } 749 750 /** 751 * Return a complete list of "groups in groups" 752 * 753 * @param string $group The group to get the list from 754 * @return array 755 */ 756 public function recursive_groups($group){ 757 if ($group===NULL){ return (false); } 758 759 $ret_groups=array(); 760 761 $groups=$this->group_info($group,array("memberof")); 762 if (isset($groups[0]["memberof"]) && is_array($groups[0]["memberof"])) { 763 $groups=$groups[0]["memberof"]; 764 765 if ($groups){ 766 $group_names=$this->nice_names($groups); 767 $ret_groups=array_merge($ret_groups,$group_names); //final groups to return 768 769 foreach ($group_names as $id => $group_name){ 770 $child_groups=$this->recursive_groups($group_name); 771 $ret_groups=array_merge($ret_groups,$child_groups); 772 } 773 } 774 } 775 776 return ($ret_groups); 777 } 778 779 /** 780 * Returns a complete list of the groups in AD based on a SAM Account Type 781 * 782 * @param string $samaccounttype The account type to return 783 * @param bool $include_desc Whether to return a description 784 * @param string $search Search parameters 785 * @param bool $sorted Whether to sort the results 786 * @return array 787 */ 788 public function search_groups($samaccounttype = ADLDAP_SECURITY_GLOBAL_GROUP, $include_desc = false, $search = "*", $sorted = true) { 789 if (!$this->_bind){ return (false); } 790 791 $filter = '(&(objectCategory=group)'; 792 if ($samaccounttype !== null) { 793 $filter .= '(samaccounttype='. $samaccounttype .')'; 794 } 795 $filter .= '(cn='.$search.'))'; 796 // Perform the search and grab all their details 797 $fields=array("samaccountname","description"); 798 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 799 $entries = ldap_get_entries($this->_conn, $sr); 800 801 $groups_array = array(); 802 for ($i=0; $i<$entries["count"]; $i++){ 803 if ($include_desc && strlen($entries[$i]["description"][0]) > 0 ){ 804 $groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["description"][0]; 805 } elseif ($include_desc){ 806 $groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0]; 807 } else { 808 array_push($groups_array, $entries[$i]["samaccountname"][0]); 809 } 810 } 811 if( $sorted ){ asort($groups_array); } 812 return ($groups_array); 813 } 814 815 /** 816 * Returns a complete list of all groups in AD 817 * 818 * @param bool $include_desc Whether to return a description 819 * @param string $search Search parameters 820 * @param bool $sorted Whether to sort the results 821 * @return array 822 */ 823 public function all_groups($include_desc = false, $search = "*", $sorted = true){ 824 $groups_array = $this->search_groups(null, $include_desc, $search, $sorted); 825 return ($groups_array); 826 } 827 828 /** 829 * Returns a complete list of security groups in AD 830 * 831 * @param bool $include_desc Whether to return a description 832 * @param string $search Search parameters 833 * @param bool $sorted Whether to sort the results 834 * @return array 835 */ 836 public function all_security_groups($include_desc = false, $search = "*", $sorted = true){ 837 $groups_array = $this->search_groups(ADLDAP_SECURITY_GLOBAL_GROUP, $include_desc, $search, $sorted); 838 return ($groups_array); 839 } 840 841 /** 842 * Returns a complete list of distribution lists in AD 843 * 844 * @param bool $include_desc Whether to return a description 845 * @param string $search Search parameters 846 * @param bool $sorted Whether to sort the results 847 * @return array 848 */ 849 public function all_distribution_groups($include_desc = false, $search = "*", $sorted = true){ 850 $groups_array = $this->search_groups(ADLDAP_DISTRIBUTION_GROUP, $include_desc, $search, $sorted); 851 return ($groups_array); 852 } 853 854 //***************************************************************************************************************** 855 // USER FUNCTIONS 856 857 /** 858 * Create a user 859 * 860 * If you specify a password here, this can only be performed over SSL 861 * 862 * @param array $attributes The attributes to set to the user account 863 * @return bool 864 */ 865 public function user_create($attributes){ 866 // Check for compulsory fields 867 if (!array_key_exists("username",$attributes)){ return ("Missing compulsory field [username]"); } 868 if (!array_key_exists("firstname",$attributes)){ return ("Missing compulsory field [firstname]"); } 869 if (!array_key_exists("surname",$attributes)){ return ("Missing compulsory field [surname]"); } 870 if (!array_key_exists("email",$attributes)){ return ("Missing compulsory field [email]"); } 871 if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } 872 if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } 873 874 if (array_key_exists("password",$attributes) && (!$this->_use_ssl && !$this->_use_tls)){ 875 throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); 876 } 877 878 if (!array_key_exists("display_name",$attributes)){ $attributes["display_name"]=$attributes["firstname"]." ".$attributes["surname"]; } 879 880 // Translate the schema 881 $add=$this->adldap_schema($attributes); 882 883 // Additional stuff only used for adding accounts 884 $add["cn"][0]=$attributes["display_name"]; 885 $add["samaccountname"][0]=$attributes["username"]; 886 $add["objectclass"][0]="top"; 887 $add["objectclass"][1]="person"; 888 $add["objectclass"][2]="organizationalPerson"; 889 $add["objectclass"][3]="user"; //person? 890 //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; 891 892 // Set the account control attribute 893 $control_options=array("NORMAL_ACCOUNT"); 894 if (!$attributes["enabled"]){ $control_options[]="ACCOUNTDISABLE"; } 895 $add["userAccountControl"][0]=$this->account_control($control_options); 896 //echo ("<pre>"); print_r($add); 897 898 // Determine the container 899 $attributes["container"]=array_reverse($attributes["container"]); 900 $container="OU=".implode(",OU=",$attributes["container"]); 901 902 // Add the entry 903 $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add); 904 if ($result!=true){ return (false); } 905 906 return (true); 907 } 908 909 /** 910 * Delete a user account 911 * 912 * @param string $username The username to delete (please be careful here!) 913 * @param bool $isGUID Is the username a GUID or a samAccountName 914 * @return array 915 */ 916 public function user_delete($username,$isGUID=false) { 917 $userinfo = $this->user_info($username, array("*"),$isGUID); 918 $dn = $userinfo[0]['distinguishedname'][0]; 919 $result=$this->dn_delete($dn); 920 if ($result!=true){ return (false); } 921 return (true); 922 } 923 924 /** 925 * Groups the user is a member of 926 * 927 * @param string $username The username to query 928 * @param bool $recursive Recursive list of groups 929 * @param bool $isGUID Is the username passed a GUID or a samAccountName 930 * @return array 931 */ 932 public function user_groups($username,$recursive=NULL,$isGUID=false){ 933 if ($username===NULL){ return (false); } 934 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it 935 if (!$this->_bind){ return (false); } 936 937 // Search the directory for their information 938 $info=@$this->user_info($username,array("memberof","primarygroupid"),$isGUID); 939 $groups=$this->nice_names($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames) 940 941 if ($recursive === true){ 942 foreach ($groups as $id => $group_name){ 943 $extra_groups=$this->recursive_groups($group_name); 944 $groups=array_merge($groups,$extra_groups); 945 } 946 } 947 948 return ($groups); 949 } 950 951 /** 952 * Find information about the users 953 * 954 * @param string $username The username to query 955 * @param array $fields Array of parameters to query 956 * @param bool $isGUID Is the username passed a GUID or a samAccountName 957 * @return array 958 */ 959 public function user_info($username,$fields=NULL,$isGUID=false){ 960 if ($username===NULL){ return (false); } 961 if (!$this->_bind){ return (false); } 962 963 if ($isGUID === true) { 964 $username = $this->strguid2hex($username); 965 $filter="objectguid=".$username; 966 } 967 else if (strstr($username, "@")) { 968 $filter="userPrincipalName=".$username; 969 } 970 else { 971 $filter="samaccountname=".$username; 972 } 973 $filter = "(&(objectCategory=person)({$filter}))"; 974 if ($fields===NULL){ $fields=array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); } 975 if (!in_array("objectsid",$fields)){ 976 $fields[] = "objectsid"; 977 } 978 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 979 $entries = ldap_get_entries($this->_conn, $sr); 980 981 if (isset($entries[0])) { 982 if ($entries[0]['count'] >= 1) { 983 if (in_array("memberof", $fields)) { 984 // AD does not return the primary group in the ldap query, we may need to fudge it 985 if ($this->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])){ 986 //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); 987 $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); 988 } else { 989 $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn; 990 } 991 $entries[0]["memberof"]["count"]++; 992 } 993 } 994 return $entries; 995 } 996 return false; 997 } 998 999 /** 1000 * Determine if a user is in a specific group 1001 * 1002 * @param string $username The username to query 1003 * @param string $group The name of the group to check against 1004 * @param bool $recursive Check groups recursively 1005 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1006 * @return bool 1007 */ 1008 public function user_ingroup($username,$group,$recursive=NULL,$isGUID=false){ 1009 if ($username===NULL){ return (false); } 1010 if ($group===NULL){ return (false); } 1011 if (!$this->_bind){ return (false); } 1012 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it 1013 1014 // Get a list of the groups 1015 $groups=$this->user_groups($username,$recursive,$isGUID); 1016 1017 // Return true if the specified group is in the group list 1018 if (in_array($group,$groups)){ return (true); } 1019 1020 return (false); 1021 } 1022 1023 /** 1024 * Return info about the domain itself 1025 * 1026 * @authot Andreas Gohr <gohr@cosmocode.de> 1027 * @param array $fields The fields to query 1028 * @return array 1029 */ 1030 public function domain_info($fields){ 1031 if (!$this->_bind){ return (false); } 1032 1033 $sr = ldap_read($this->_conn, $this->_base_dn, 'objectclass=*', $fields); 1034 if (!$sr) { 1035 return false; 1036 } 1037 $info = ldap_get_entries($this->_conn, $sr); 1038 if(count($info)) return $info[0]; 1039 1040 return false; 1041 } 1042 1043 /** 1044 * Determine a user's password expiry date 1045 * 1046 * @param string $username The username to query 1047 * @param book $isGUID Is the username passed a GUID or a samAccountName 1048 * @requires bcmath http://www.php.net/manual/en/book.bc.php 1049 * @return array 1050 */ 1051 public function user_password_expiry($username,$isGUID=false) { 1052 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1053 if (!$this->_bind){ return (false); } 1054 if (!function_exists('bcmod')) { return ("Missing function support [bcmod] http://www.php.net/manual/en/book.bc.php"); }; 1055 1056 $userinfo = $this->user_info($username, array("pwdlastset", "useraccountcontrol"), $isGUID); 1057 $pwdlastset = $userinfo[0]['pwdlastset'][0]; 1058 $status = array(); 1059 1060 if ($userinfo[0]['useraccountcontrol'][0] == '66048') { 1061 // Password does not expire 1062 return "Does not expire"; 1063 } 1064 if ($pwdlastset === '0') { 1065 // Password has already expired 1066 return "Password has expired"; 1067 } 1068 1069 // Password expiry in AD can be calculated from TWO values: 1070 // - User's own pwdLastSet attribute: stores the last time the password was changed 1071 // - Domain's maxPwdAge attribute: how long passwords last in the domain 1072 // 1073 // Although Microsoft chose to use a different base and unit for time measurements. 1074 // This function will convert them to Unix timestamps 1075 $sr = ldap_read($this->_conn, $this->_base_dn, 'objectclass=*', array('maxPwdAge')); 1076 if (!$sr) { 1077 return false; 1078 } 1079 $info = ldap_get_entries($this->_conn, $sr); 1080 $maxpwdage = $info[0]['maxpwdage'][0]; 1081 1082 1083 // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx 1084 // 1085 // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC), 1086 // stored in a 64 bit integer. 1087 // 1088 // The number of seconds between this date and Unix epoch is 11644473600. 1089 // 1090 // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond 1091 // intervals from the time the password was set before the password expires. 1092 // 1093 // We also need to scale this to seconds but also this value is a _negative_ quantity! 1094 // 1095 // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire 1096 // 1097 // Unfortunately the maths involved are too big for PHP integers, so I've had to require 1098 // BCMath functions to work with arbitrary precision numbers. 1099 if (bcmod($maxpwdage, 4294967296) === '0') { 1100 return "Domain does not expire passwords"; 1101 } 1102 1103 // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's 1104 // time units. Because maxpwd age is negative we need to subtract it. 1105 $pwdexpire = bcsub($pwdlastset, $maxpwdage); 1106 1107 // Convert MS's time to Unix time 1108 $status['expiryts'] = bcsub(bcdiv($pwdexpire, '10000000'), '11644473600'); 1109 $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdexpire, '10000000'), '11644473600')); 1110 1111 return $status; 1112 } 1113 1114 /** 1115 * Modify a user 1116 * 1117 * @param string $username The username to query 1118 * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes 1119 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1120 * @return bool 1121 */ 1122 public function user_modify($username,$attributes,$isGUID=false){ 1123 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1124 if (array_key_exists("password",$attributes) && !$this->_use_ssl){ 1125 throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); 1126 } 1127 1128 // Find the dn of the user 1129 $user_dn=$this->user_dn($username,$isGUID); 1130 if ($user_dn===false){ return (false); } 1131 1132 // Translate the update to the LDAP schema 1133 $mod=$this->adldap_schema($attributes); 1134 1135 // Check to see if this is an enabled status update 1136 if (!$mod && !array_key_exists("enabled", $attributes)){ return (false); } 1137 1138 // Set the account control attribute (only if specified) 1139 if (array_key_exists("enabled",$attributes)){ 1140 if ($attributes["enabled"]){ $control_options=array("NORMAL_ACCOUNT"); } 1141 else { $control_options=array("NORMAL_ACCOUNT","ACCOUNTDISABLE"); } 1142 $mod["userAccountControl"][0]=$this->account_control($control_options); 1143 } 1144 1145 // Do the update 1146 $result=@ldap_modify($this->_conn,$user_dn,$mod); 1147 if ($result==false){ return (false); } 1148 1149 return (true); 1150 } 1151 1152 /** 1153 * Disable a user account 1154 * 1155 * @param string $username The username to disable 1156 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1157 * @return bool 1158 */ 1159 public function user_disable($username,$isGUID=false){ 1160 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1161 $attributes=array("enabled"=>0); 1162 $result = $this->user_modify($username, $attributes, $isGUID); 1163 if ($result==false){ return (false); } 1164 1165 return (true); 1166 } 1167 1168 /** 1169 * Enable a user account 1170 * 1171 * @param string $username The username to enable 1172 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1173 * @return bool 1174 */ 1175 public function user_enable($username,$isGUID=false){ 1176 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1177 $attributes=array("enabled"=>1); 1178 $result = $this->user_modify($username, $attributes, $isGUID); 1179 if ($result==false){ return (false); } 1180 1181 return (true); 1182 } 1183 1184 /** 1185 * Set the password of a user - This must be performed over SSL 1186 * 1187 * @param string $username The username to modify 1188 * @param string $password The new password 1189 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1190 * @return bool 1191 */ 1192 public function user_password($username,$password,$isGUID=false){ 1193 if ($username===NULL){ return (false); } 1194 if ($password===NULL){ return (false); } 1195 if (!$this->_bind){ return (false); } 1196 if (!$this->_use_ssl && !$this->_use_tls){ 1197 throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); 1198 } 1199 1200 $user_dn=$this->user_dn($username,$isGUID); 1201 if ($user_dn===false){ return (false); } 1202 1203 $add=array(); 1204 $add["unicodePwd"][0]=$this->encode_password($password); 1205 1206 $result=@ldap_mod_replace($this->_conn,$user_dn,$add); 1207 if ($result==false){ 1208 $err = ldap_errno($this->_conn); 1209 if($err){ 1210 $msg = 'Error '.$err.': '.ldap_err2str($err).'.'; 1211 if($err == 53) $msg .= ' Your password might not match the password policy.'; 1212 throw new adLDAPException($msg); 1213 }else{ 1214 return false; 1215 } 1216 } 1217 1218 return (true); 1219 } 1220 1221 /** 1222 * Return a list of all users in AD 1223 * 1224 * @param bool $include_desc Return a description of the user 1225 * @param string $search Search parameter 1226 * @param bool $sorted Sort the user accounts 1227 * @return array 1228 */ 1229 public function all_users($include_desc = false, $search = "*", $sorted = true){ 1230 if (!$this->_bind){ return (false); } 1231 1232 // Perform the search and grab all their details 1233 $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=".$search."))"; 1234 $fields=array("samaccountname","displayname"); 1235 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 1236 $entries = ldap_get_entries($this->_conn, $sr); 1237 1238 $users_array = array(); 1239 for ($i=0; $i<$entries["count"]; $i++){ 1240 if ($include_desc && strlen($entries[$i]["displayname"][0])>0){ 1241 $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["displayname"][0]; 1242 } elseif ($include_desc){ 1243 $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0]; 1244 } else { 1245 array_push($users_array, $entries[$i]["samaccountname"][0]); 1246 } 1247 } 1248 if ($sorted){ asort($users_array); } 1249 return ($users_array); 1250 } 1251 1252 /** 1253 * Converts a username (samAccountName) to a GUID 1254 * 1255 * @param string $username The username to query 1256 * @return string 1257 */ 1258 public function username2guid($username) { 1259 if (!$this->_bind){ return (false); } 1260 if ($username === null){ return ("Missing compulsory field [username]"); } 1261 1262 $filter = "samaccountname=" . $username; 1263 $fields = array("objectGUID"); 1264 $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields); 1265 if (ldap_count_entries($this->_conn, $sr) > 0) { 1266 $entry = @ldap_first_entry($this->_conn, $sr); 1267 $guid = @ldap_get_values_len($this->_conn, $entry, 'objectGUID'); 1268 $strGUID = $this->binary2text($guid[0]); 1269 return ($strGUID); 1270 } 1271 else { 1272 return (false); 1273 } 1274 } 1275 1276 /** 1277 * Move a user account to a different OU 1278 * 1279 * @param string $username The username to move (please be careful here!) 1280 * @param array $container The container or containers to move the user to (please be careful here!). 1281 * accepts containers in 1. parent 2. child order 1282 * @return array 1283 */ 1284 public function user_move($username, $container) { 1285 if (!$this->_bind){ return (false); } 1286 if ($username === null){ return ("Missing compulsory field [username]"); } 1287 if ($container === null){ return ("Missing compulsory field [container]"); } 1288 if (!is_array($container)){ return ("Container must be an array"); } 1289 1290 $userinfo = $this->user_info($username, array("*")); 1291 $dn = $userinfo[0]['distinguishedname'][0]; 1292 $newrdn = "cn=" . $username; 1293 $container = array_reverse($container); 1294 $newcontainer = "ou=" . implode(",ou=",$container); 1295 $newbasedn = strtolower($newcontainer) . "," . $this->_base_dn; 1296 $result=@ldap_rename($this->_conn,$dn,$newrdn,$newbasedn,true); 1297 if ($result !== true) { 1298 return (false); 1299 } 1300 return (true); 1301 } 1302 1303 //***************************************************************************************************************** 1304 // CONTACT FUNCTIONS 1305 // * Still work to do in this area, and new functions to write 1306 1307 /** 1308 * Create a contact 1309 * 1310 * @param array $attributes The attributes to set to the contact 1311 * @return bool 1312 */ 1313 public function contact_create($attributes){ 1314 // Check for compulsory fields 1315 if (!array_key_exists("display_name",$attributes)){ return ("Missing compulsory field [display_name]"); } 1316 if (!array_key_exists("email",$attributes)){ return ("Missing compulsory field [email]"); } 1317 if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } 1318 if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } 1319 1320 // Translate the schema 1321 $add=$this->adldap_schema($attributes); 1322 1323 // Additional stuff only used for adding contacts 1324 $add["cn"][0]=$attributes["display_name"]; 1325 $add["objectclass"][0]="top"; 1326 $add["objectclass"][1]="person"; 1327 $add["objectclass"][2]="organizationalPerson"; 1328 $add["objectclass"][3]="contact"; 1329 if (!isset($attributes['exchange_hidefromlists'])) { 1330 $add["msExchHideFromAddressLists"][0]="TRUE"; 1331 } 1332 1333 // Determine the container 1334 $attributes["container"]=array_reverse($attributes["container"]); 1335 $container="OU=".implode(",OU=",$attributes["container"]); 1336 1337 // Add the entry 1338 $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add); 1339 if ($result!=true){ return (false); } 1340 1341 return (true); 1342 } 1343 1344 /** 1345 * Determine the list of groups a contact is a member of 1346 * 1347 * @param string $distinguisedname The full DN of a contact 1348 * @param bool $recursive Recursively check groups 1349 * @return array 1350 */ 1351 public function contact_groups($distinguishedname,$recursive=NULL){ 1352 if ($distinguishedname===NULL){ return (false); } 1353 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it 1354 if (!$this->_bind){ return (false); } 1355 1356 // Search the directory for their information 1357 $info=@$this->contact_info($distinguishedname,array("memberof","primarygroupid")); 1358 $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our contact 1359 1360 if ($recursive === true){ 1361 foreach ($groups as $id => $group_name){ 1362 $extra_groups=$this->recursive_groups($group_name); 1363 $groups=array_merge($groups,$extra_groups); 1364 } 1365 } 1366 1367 return ($groups); 1368 } 1369 1370 /** 1371 * Get contact information 1372 * 1373 * @param string $distinguisedname The full DN of a contact 1374 * @param array $fields Attributes to be returned 1375 * @return array 1376 */ 1377 public function contact_info($distinguishedname,$fields=NULL){ 1378 if ($distinguishedname===NULL){ return (false); } 1379 if (!$this->_bind){ return (false); } 1380 1381 $filter="distinguishedName=".$distinguishedname; 1382 if ($fields===NULL){ $fields=array("distinguishedname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); } 1383 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 1384 $entries = ldap_get_entries($this->_conn, $sr); 1385 1386 if ($entries[0]['count'] >= 1) { 1387 // AD does not return the primary group in the ldap query, we may need to fudge it 1388 if ($this->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["primarygroupid"][0])){ 1389 //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); 1390 $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); 1391 } else { 1392 $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn; 1393 } 1394 } 1395 1396 $entries[0]["memberof"]["count"]++; 1397 return ($entries); 1398 } 1399 1400 /** 1401 * Determine if a contact is a member of a group 1402 * 1403 * @param string $distinguisedname The full DN of a contact 1404 * @param string $group The group name to query 1405 * @param bool $recursive Recursively check groups 1406 * @return bool 1407 */ 1408 public function contact_ingroup($distinguisedname,$group,$recursive=NULL){ 1409 if ($distinguisedname===NULL){ return (false); } 1410 if ($group===NULL){ return (false); } 1411 if (!$this->_bind){ return (false); } 1412 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it 1413 1414 // Get a list of the groups 1415 $groups=$this->contact_groups($distinguisedname,array("memberof"),$recursive); 1416 1417 // Return true if the specified group is in the group list 1418 if (in_array($group,$groups)){ return (true); } 1419 1420 return (false); 1421 } 1422 1423 /** 1424 * Modify a contact 1425 * 1426 * @param string $distinguishedname The contact to query 1427 * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes 1428 * @return bool 1429 */ 1430 public function contact_modify($distinguishedname,$attributes){ 1431 if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); } 1432 1433 // Translate the update to the LDAP schema 1434 $mod=$this->adldap_schema($attributes); 1435 1436 // Check to see if this is an enabled status update 1437 if (!$mod){ return (false); } 1438 1439 // Do the update 1440 $result=ldap_modify($this->_conn,$distinguishedname,$mod); 1441 if ($result==false){ return (false); } 1442 1443 return (true); 1444 } 1445 1446 /** 1447 * Delete a contact 1448 * 1449 * @param string $distinguishedname The contact dn to delete (please be careful here!) 1450 * @return array 1451 */ 1452 public function contact_delete($distinguishedname) { 1453 $result = $this->dn_delete($distinguishedname); 1454 if ($result!=true){ return (false); } 1455 return (true); 1456 } 1457 1458 /** 1459 * Return a list of all contacts 1460 * 1461 * @param bool $include_desc Include a description of a contact 1462 * @param string $search The search parameters 1463 * @param bool $sorted Whether to sort the results 1464 * @return array 1465 */ 1466 public function all_contacts($include_desc = false, $search = "*", $sorted = true){ 1467 if (!$this->_bind){ return (false); } 1468 1469 // Perform the search and grab all their details 1470 $filter = "(&(objectClass=contact)(cn=".$search."))"; 1471 $fields=array("displayname","distinguishedname"); 1472 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 1473 $entries = ldap_get_entries($this->_conn, $sr); 1474 1475 $users_array = array(); 1476 for ($i=0; $i<$entries["count"]; $i++){ 1477 if ($include_desc && strlen($entries[$i]["displayname"][0])>0){ 1478 $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["displayname"][0]; 1479 } elseif ($include_desc){ 1480 $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["distinguishedname"][0]; 1481 } else { 1482 array_push($users_array, $entries[$i]["distinguishedname"][0]); 1483 } 1484 } 1485 if ($sorted){ asort($users_array); } 1486 return ($users_array); 1487 } 1488 1489 //***************************************************************************************************************** 1490 // FOLDER FUNCTIONS 1491 1492 /** 1493 * Returns a folder listing for a specific OU 1494 * See http://adldap.sourceforge.net/wiki/doku.php?id=api_folder_functions 1495 * 1496 * @param array $folder_name An array to the OU you wish to list. 1497 * If set to NULL will list the root, strongly recommended to set 1498 * $recursive to false in that instance! 1499 * @param string $dn_type The type of record to list. This can be ADLDAP_FOLDER or ADLDAP_CONTAINER. 1500 * @param bool $recursive Recursively search sub folders 1501 * @param bool $type Specify a type of object to search for 1502 * @return array 1503 */ 1504 public function folder_list($folder_name = NULL, $dn_type = ADLDAP_FOLDER, $recursive = NULL, $type = NULL) { 1505 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it 1506 if (!$this->_bind){ return (false); } 1507 1508 $filter = '(&'; 1509 if ($type !== NULL) { 1510 switch ($type) { 1511 case 'contact': 1512 $filter .= '(objectClass=contact)'; 1513 break; 1514 case 'computer': 1515 $filter .= '(objectClass=computer)'; 1516 break; 1517 case 'group': 1518 $filter .= '(objectClass=group)'; 1519 break; 1520 case 'folder': 1521 $filter .= '(objectClass=organizationalUnit)'; 1522 break; 1523 case 'container': 1524 $filter .= '(objectClass=container)'; 1525 break; 1526 case 'domain': 1527 $filter .= '(objectClass=builtinDomain)'; 1528 break; 1529 default: 1530 $filter .= '(objectClass=user)'; 1531 break; 1532 } 1533 } 1534 else { 1535 $filter .= '(objectClass=*)'; 1536 } 1537 // If the folder name is null then we will search the root level of AD 1538 // This requires us to not have an OU= part, just the base_dn 1539 $searchou = $this->_base_dn; 1540 if (is_array($folder_name)) { 1541 $ou = $dn_type . "=".implode("," . $dn_type . "=",$folder_name); 1542 $filter .= '(!(distinguishedname=' . $ou . ',' . $this->_base_dn . ')))'; 1543 $searchou = $ou . ',' . $this->_base_dn; 1544 } 1545 else { 1546 $filter .= '(!(distinguishedname=' . $this->_base_dn . ')))'; 1547 } 1548 1549 if ($recursive === true) { 1550 $sr=ldap_search($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname')); 1551 $entries = @ldap_get_entries($this->_conn, $sr); 1552 if (is_array($entries)) { 1553 return $entries; 1554 } 1555 } 1556 else { 1557 $sr=ldap_list($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname')); 1558 $entries = @ldap_get_entries($this->_conn, $sr); 1559 if (is_array($entries)) { 1560 return $entries; 1561 } 1562 } 1563 1564 return false; 1565 } 1566 1567 //***************************************************************************************************************** 1568 // COMPUTER FUNCTIONS 1569 1570 /** 1571 * Get information about a specific computer 1572 * 1573 * @param string $computer_name The name of the computer 1574 * @param array $fields Attributes to return 1575 * @return array 1576 */ 1577 public function computer_info($computer_name,$fields=NULL){ 1578 if ($computer_name===NULL){ return (false); } 1579 if (!$this->_bind){ return (false); } 1580 1581 $filter="(&(objectClass=computer)(cn=".$computer_name."))"; 1582 if ($fields===NULL){ $fields=array("memberof","cn","displayname","dnshostname","distinguishedname","objectcategory","operatingsystem","operatingsystemservicepack","operatingsystemversion"); } 1583 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 1584 $entries = ldap_get_entries($this->_conn, $sr); 1585 1586 return ($entries); 1587 } 1588 1589 /** 1590 * Check if a computer is in a group 1591 * 1592 * @param string $computer_name The name of the computer 1593 * @param string $group The group to check 1594 * @param bool $recursive Whether to check recursively 1595 * @return array 1596 */ 1597 public function computer_ingroup($computer_name,$group,$recursive=NULL){ 1598 if ($computer_name===NULL){ return (false); } 1599 if ($group===NULL){ return (false); } 1600 if (!$this->_bind){ return (false); } 1601 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // use the default option if they haven't set it 1602 1603 //get a list of the groups 1604 $groups=$this->computer_groups($computer_name,array("memberof"),$recursive); 1605 1606 //return true if the specified group is in the group list 1607 if (in_array($group,$groups)){ return (true); } 1608 1609 return (false); 1610 } 1611 1612 /** 1613 * Get the groups a computer is in 1614 * 1615 * @param string $computer_name The name of the computer 1616 * @param bool $recursive Whether to check recursively 1617 * @return array 1618 */ 1619 public function computer_groups($computer_name,$recursive=NULL){ 1620 if ($computer_name===NULL){ return (false); } 1621 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it 1622 if (!$this->_bind){ return (false); } 1623 1624 //search the directory for their information 1625 $info=@$this->computer_info($computer_name,array("memberof","primarygroupid")); 1626 $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our guy (unique usernames) 1627 1628 if ($recursive === true){ 1629 foreach ($groups as $id => $group_name){ 1630 $extra_groups=$this->recursive_groups($group_name); 1631 $groups=array_merge($groups,$extra_groups); 1632 } 1633 } 1634 1635 return ($groups); 1636 } 1637 1638 //************************************************************************************************************ 1639 // ORGANIZATIONAL UNIT FUNCTIONS 1640 1641 /** 1642 * Create an organizational unit 1643 * 1644 * @param array $attributes Default attributes of the ou 1645 * @return bool 1646 */ 1647 public function ou_create($attributes){ 1648 if (!is_array($attributes)){ return ("Attributes must be an array"); } 1649 if (!array_key_exists("ou_name",$attributes)){ return ("Missing compulsory field [ou_name]"); } 1650 if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } 1651 if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } 1652 $attributes["container"]=array_reverse($attributes["container"]); 1653 1654 $add=array(); 1655 $add["objectClass"] = "organizationalUnit"; 1656 1657 $container="OU=".implode(",OU=",$attributes["container"]); 1658 $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add); 1659 if ($result!=true){ return (false); } 1660 1661 return (true); 1662 } 1663 1664 //************************************************************************************************************ 1665 // EXCHANGE FUNCTIONS 1666 1667 /** 1668 * Create an Exchange account 1669 * 1670 * @param string $username The username of the user to add the Exchange account to 1671 * @param array $storagegroup The mailbox, Exchange Storage Group, for the user account, this must be a full CN 1672 * If the storage group has a different base_dn to the adLDAP configuration, set it using $base_dn 1673 * @param string $emailaddress The primary email address to add to this user 1674 * @param string $mailnickname The mail nick name. If mail nickname is blank, the username will be used 1675 * @param bool $usedefaults Indicates whether the store should use the default quota, rather than the per-mailbox quota. 1676 * @param string $base_dn Specify an alternative base_dn for the Exchange storage group 1677 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1678 * @return bool 1679 */ 1680 public function exchange_create_mailbox($username, $storagegroup, $emailaddress, $mailnickname=NULL, $usedefaults=TRUE, $base_dn=NULL, $isGUID=false){ 1681 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1682 if ($storagegroup===NULL){ return ("Missing compulsory array [storagegroup]"); } 1683 if (!is_array($storagegroup)){ return ("[storagegroup] must be an array"); } 1684 if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); } 1685 1686 if ($base_dn===NULL) { 1687 $base_dn = $this->_base_dn; 1688 } 1689 1690 $container="CN=".implode(",CN=",$storagegroup); 1691 1692 if ($mailnickname===NULL) { $mailnickname=$username; } 1693 $mdbUseDefaults = $this->bool2str($usedefaults); 1694 1695 $attributes = array( 1696 'exchange_homemdb'=>$container.",".$base_dn, 1697 'exchange_proxyaddress'=>'SMTP:' . $emailaddress, 1698 'exchange_mailnickname'=>$mailnickname, 1699 'exchange_usedefaults'=>$mdbUseDefaults 1700 ); 1701 $result = $this->user_modify($username,$attributes,$isGUID); 1702 if ($result==false){ return (false); } 1703 return (true); 1704 } 1705 1706 /** 1707 * Add an X400 address to Exchange 1708 * See http://tools.ietf.org/html/rfc1685 for more information. 1709 * An X400 Address looks similar to this X400:c=US;a= ;p=Domain;o=Organization;s=Doe;g=John; 1710 * 1711 * @param string $username The username of the user to add the X400 to to 1712 * @param string $country Country 1713 * @param string $admd Administration Management Domain 1714 * @param string $pdmd Private Management Domain (often your AD domain) 1715 * @param string $org Organization 1716 * @param string $surname Surname 1717 * @param string $givenName Given name 1718 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1719 * @return bool 1720 */ 1721 public function exchange_add_X400($username, $country, $admd, $pdmd, $org, $surname, $givenname, $isGUID=false) { 1722 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1723 1724 $proxyvalue = 'X400:'; 1725 1726 // Find the dn of the user 1727 $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID); 1728 if ($user[0]["dn"]===NULL){ return (false); } 1729 $user_dn=$user[0]["dn"]; 1730 1731 // We do not have to demote an email address from the default so we can just add the new proxy address 1732 $attributes['exchange_proxyaddress'] = $proxyvalue . 'c=' . $country . ';a=' . $admd . ';p=' . $pdmd . ';o=' . $org . ';s=' . $surname . ';g=' . $givenname . ';'; 1733 1734 // Translate the update to the LDAP schema 1735 $add=$this->adldap_schema($attributes); 1736 1737 if (!$add){ return (false); } 1738 1739 // Do the update 1740 // Take out the @ to see any errors, usually this error might occur because the address already 1741 // exists in the list of proxyAddresses 1742 $result=@ldap_mod_add($this->_conn,$user_dn,$add); 1743 if ($result==false){ return (false); } 1744 1745 return (true); 1746 } 1747 1748 /** 1749 * Add an address to Exchange 1750 * 1751 * @param string $username The username of the user to add the Exchange account to 1752 * @param string $emailaddress The email address to add to this user 1753 * @param bool $default Make this email address the default address, this is a bit more intensive as we have to demote any existing default addresses 1754 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1755 * @return bool 1756 */ 1757 public function exchange_add_address($username, $emailaddress, $default=FALSE, $isGUID=false) { 1758 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1759 if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } 1760 1761 $proxyvalue = 'smtp:'; 1762 if ($default === true) { 1763 $proxyvalue = 'SMTP:'; 1764 } 1765 1766 // Find the dn of the user 1767 $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID); 1768 if ($user[0]["dn"]===NULL){ return (false); } 1769 $user_dn=$user[0]["dn"]; 1770 1771 // We need to scan existing proxy addresses and demote the default one 1772 if (is_array($user[0]["proxyaddresses"]) && $default===true) { 1773 $modaddresses = array(); 1774 for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) { 1775 if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false) { 1776 $user[0]['proxyaddresses'][$i] = str_replace('SMTP:', 'smtp:', $user[0]['proxyaddresses'][$i]); 1777 } 1778 if ($user[0]['proxyaddresses'][$i] != '') { 1779 $modaddresses['proxyAddresses'][$i] = $user[0]['proxyaddresses'][$i]; 1780 } 1781 } 1782 $modaddresses['proxyAddresses'][(sizeof($user[0]['proxyaddresses'])-1)] = 'SMTP:' . $emailaddress; 1783 1784 $result=@ldap_mod_replace($this->_conn,$user_dn,$modaddresses); 1785 if ($result==false){ return (false); } 1786 1787 return (true); 1788 } 1789 else { 1790 // We do not have to demote an email address from the default so we can just add the new proxy address 1791 $attributes['exchange_proxyaddress'] = $proxyvalue . $emailaddress; 1792 1793 // Translate the update to the LDAP schema 1794 $add=$this->adldap_schema($attributes); 1795 1796 if (!$add){ return (false); } 1797 1798 // Do the update 1799 // Take out the @ to see any errors, usually this error might occur because the address already 1800 // exists in the list of proxyAddresses 1801 $result=@ldap_mod_add($this->_conn,$user_dn,$add); 1802 if ($result==false){ return (false); } 1803 1804 return (true); 1805 } 1806 } 1807 1808 /** 1809 * Remove an address to Exchange 1810 * If you remove a default address the account will no longer have a default, 1811 * we recommend changing the default address first 1812 * 1813 * @param string $username The username of the user to add the Exchange account to 1814 * @param string $emailaddress The email address to add to this user 1815 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1816 * @return bool 1817 */ 1818 public function exchange_del_address($username, $emailaddress, $isGUID=false) { 1819 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1820 if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } 1821 1822 // Find the dn of the user 1823 $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID); 1824 if ($user[0]["dn"]===NULL){ return (false); } 1825 $user_dn=$user[0]["dn"]; 1826 1827 if (is_array($user[0]["proxyaddresses"])) { 1828 $mod = array(); 1829 for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) { 1830 if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false && $user[0]['proxyaddresses'][$i] == 'SMTP:' . $emailaddress) { 1831 $mod['proxyAddresses'][0] = 'SMTP:' . $emailaddress; 1832 } 1833 elseif (strstr($user[0]['proxyaddresses'][$i], 'smtp:') !== false && $user[0]['proxyaddresses'][$i] == 'smtp:' . $emailaddress) { 1834 $mod['proxyAddresses'][0] = 'smtp:' . $emailaddress; 1835 } 1836 } 1837 1838 $result=@ldap_mod_del($this->_conn,$user_dn,$mod); 1839 if ($result==false){ return (false); } 1840 1841 return (true); 1842 } 1843 else { 1844 return (false); 1845 } 1846 } 1847 /** 1848 * Change the default address 1849 * 1850 * @param string $username The username of the user to add the Exchange account to 1851 * @param string $emailaddress The email address to make default 1852 * @param bool $isGUID Is the username passed a GUID or a samAccountName 1853 * @return bool 1854 */ 1855 public function exchange_primary_address($username, $emailaddress, $isGUID=false) { 1856 if ($username===NULL){ return ("Missing compulsory field [username]"); } 1857 if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } 1858 1859 // Find the dn of the user 1860 $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID); 1861 if ($user[0]["dn"]===NULL){ return (false); } 1862 $user_dn=$user[0]["dn"]; 1863 1864 if (is_array($user[0]["proxyaddresses"])) { 1865 $modaddresses = array(); 1866 for ($i=0;$i<sizeof($user[0]['proxyaddresses']);$i++) { 1867 if (strstr($user[0]['proxyaddresses'][$i], 'SMTP:') !== false) { 1868 $user[0]['proxyaddresses'][$i] = str_replace('SMTP:', 'smtp:', $user[0]['proxyaddresses'][$i]); 1869 } 1870 if ($user[0]['proxyaddresses'][$i] == 'smtp:' . $emailaddress) { 1871 $user[0]['proxyaddresses'][$i] = str_replace('smtp:', 'SMTP:', $user[0]['proxyaddresses'][$i]); 1872 } 1873 if ($user[0]['proxyaddresses'][$i] != '') { 1874 $modaddresses['proxyAddresses'][$i] = $user[0]['proxyaddresses'][$i]; 1875 } 1876 } 1877 1878 $result=@ldap_mod_replace($this->_conn,$user_dn,$modaddresses); 1879 if ($result==false){ return (false); } 1880 1881 return (true); 1882 } 1883 1884 } 1885 1886 /** 1887 * Mail enable a contact 1888 * Allows email to be sent to them through Exchange 1889 * 1890 * @param string $distinguishedname The contact to mail enable 1891 * @param string $emailaddress The email address to allow emails to be sent through 1892 * @param string $mailnickname The mailnickname for the contact in Exchange. If NULL this will be set to the display name 1893 * @return bool 1894 */ 1895 public function exchange_contact_mailenable($distinguishedname, $emailaddress, $mailnickname=NULL){ 1896 if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); } 1897 if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); } 1898 1899 if ($mailnickname !== NULL) { 1900 // Find the dn of the user 1901 $user=$this->contact_info($distinguishedname,array("cn","displayname")); 1902 if ($user[0]["displayname"]===NULL){ return (false); } 1903 $mailnickname = $user[0]['displayname'][0]; 1904 } 1905 1906 $attributes = array("email"=>$emailaddress,"contact_email"=>"SMTP:" . $emailaddress,"exchange_proxyaddress"=>"SMTP:" . $emailaddress,"exchange_mailnickname"=>$mailnickname); 1907 1908 // Translate the update to the LDAP schema 1909 $mod=$this->adldap_schema($attributes); 1910 1911 // Check to see if this is an enabled status update 1912 if (!$mod){ return (false); } 1913 1914 // Do the update 1915 $result=ldap_modify($this->_conn,$distinguishedname,$mod); 1916 if ($result==false){ return (false); } 1917 1918 return (true); 1919 } 1920 1921 /** 1922 * Returns a list of Exchange Servers in the ConfigurationNamingContext of the domain 1923 * 1924 * @param array $attributes An array of the AD attributes you wish to return 1925 * @return array 1926 */ 1927 public function exchange_servers($attributes = array('cn','distinguishedname','serialnumber')) { 1928 if (!$this->_bind){ return (false); } 1929 1930 $configurationNamingContext = $this->get_root_dse(array('configurationnamingcontext')); 1931 $sr = @ldap_search($this->_conn,$configurationNamingContext[0]['configurationnamingcontext'][0],'(&(objectCategory=msExchExchangeServer))',$attributes); 1932 $entries = @ldap_get_entries($this->_conn, $sr); 1933 return $entries; 1934 } 1935 1936 /** 1937 * Returns a list of Storage Groups in Exchange for a given mail server 1938 * 1939 * @param string $exchangeServer The full DN of an Exchange server. You can use exchange_servers() to find the DN for your server 1940 * @param array $attributes An array of the AD attributes you wish to return 1941 * @param bool $recursive If enabled this will automatically query the databases within a storage group 1942 * @return array 1943 */ 1944 public function exchange_storage_groups($exchangeServer, $attributes = array('cn','distinguishedname'), $recursive = NULL) { 1945 if (!$this->_bind){ return (false); } 1946 if ($exchangeServer===NULL){ return ("Missing compulsory field [exchangeServer]"); } 1947 if ($recursive===NULL){ $recursive=$this->_recursive_groups; } 1948 1949 $filter = '(&(objectCategory=msExchStorageGroup))'; 1950 $sr=@ldap_search($this->_conn, $exchangeServer, $filter, $attributes); 1951 $entries = @ldap_get_entries($this->_conn, $sr); 1952 1953 if ($recursive === true) { 1954 for ($i=0; $i<$entries['count']; $i++) { 1955 $entries[$i]['msexchprivatemdb'] = $this->exchange_storage_databases($entries[$i]['distinguishedname'][0]); 1956 } 1957 } 1958 1959 return $entries; 1960 } 1961 1962 /** 1963 * Returns a list of Databases within any given storage group in Exchange for a given mail server 1964 * 1965 * @param string $storageGroup The full DN of an Storage Group. You can use exchange_storage_groups() to find the DN 1966 * @param array $attributes An array of the AD attributes you wish to return 1967 * @return array 1968 */ 1969 public function exchange_storage_databases($storageGroup, $attributes = array('cn','distinguishedname','displayname')) { 1970 if (!$this->_bind){ return (false); } 1971 if ($storageGroup===NULL){ return ("Missing compulsory field [storageGroup]"); } 1972 1973 $filter = '(&(objectCategory=msExchPrivateMDB))'; 1974 $sr=@ldap_search($this->_conn, $storageGroup, $filter, $attributes); 1975 $entries = @ldap_get_entries($this->_conn, $sr); 1976 return $entries; 1977 } 1978 1979 //************************************************************************************************************ 1980 // SERVER FUNCTIONS 1981 1982 /** 1983 * Find the Base DN of your domain controller 1984 * 1985 * @return string 1986 */ 1987 public function find_base_dn() { 1988 $namingContext = $this->get_root_dse(array('defaultnamingcontext')); 1989 return $namingContext[0]['defaultnamingcontext'][0]; 1990 } 1991 1992 /** 1993 * Get the RootDSE properties from a domain controller 1994 * 1995 * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext 1996 * @return array 1997 */ 1998 public function get_root_dse($attributes = array("*", "+")) { 1999 if (!$this->_bind){ return (false); } 2000 2001 $sr = @ldap_read($this->_conn, NULL, 'objectClass=*', $attributes); 2002 $entries = @ldap_get_entries($this->_conn, $sr); 2003 return $entries; 2004 } 2005 2006 //************************************************************************************************************ 2007 // UTILITY FUNCTIONS (Many of these functions are protected and can only be called from within the class) 2008 2009 /** 2010 * Get last error from Active Directory 2011 * 2012 * This function gets the last message from Active Directory 2013 * This may indeed be a 'Success' message but if you get an unknown error 2014 * it might be worth calling this function to see what errors were raised 2015 * 2016 * return string 2017 */ 2018 public function get_last_error() { 2019 return @ldap_error($this->_conn); 2020 } 2021 2022 /** 2023 * Detect LDAP support in php 2024 * 2025 * @return bool 2026 */ 2027 protected function ldap_supported() { 2028 if (!function_exists('ldap_connect')) { 2029 return (false); 2030 } 2031 return (true); 2032 } 2033 2034 /** 2035 * Schema 2036 * 2037 * @param array $attributes Attributes to be queried 2038 * @return array 2039 */ 2040 protected function adldap_schema($attributes){ 2041 2042 // LDAP doesn't like NULL attributes, only set them if they have values 2043 // If you wish to remove an attribute you should set it to a space 2044 // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute 2045 $mod=array(); 2046 2047 // Check every attribute to see if it contains 8bit characters and then UTF8 encode them 2048 array_walk($attributes, array($this, 'encode8bit')); 2049 2050 if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; } 2051 if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; } 2052 //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes? 2053 if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; } 2054 if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; } 2055 if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; } 2056 if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; } 2057 if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; } 2058 if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; } 2059 if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; } 2060 if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; } 2061 if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; } 2062 if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; } 2063 if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format? 2064 if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; } 2065 if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; } 2066 if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; } 2067 if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; } 2068 if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; } 2069 if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName*** 2070 if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; } 2071 if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->encode_password($attributes["password"]); } 2072 if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; } 2073 if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; } 2074 if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; } 2075 if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; } 2076 if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; } 2077 if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; } 2078 if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; } 2079 if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; } 2080 if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; } 2081 if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; } 2082 if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; } 2083 2084 // Distribution List specific schema 2085 if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; } 2086 if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; } 2087 2088 // Exchange Schema 2089 if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; } 2090 if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; } 2091 if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; } 2092 if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; } 2093 if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; } 2094 if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; } 2095 if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; } 2096 2097 // This schema is designed for contacts 2098 if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; } 2099 if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; } 2100 2101 //echo ("<pre>"); print_r($mod); 2102 /* 2103 // modifying a name is a bit fiddly 2104 if ($attributes["firstname"] && $attributes["surname"]){ 2105 $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"]; 2106 $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"]; 2107 $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; 2108 } 2109 */ 2110 2111 if (count($mod)==0){ return (false); } 2112 return ($mod); 2113 } 2114 2115 /** 2116 * Coping with AD not returning the primary group 2117 * http://support.microsoft.com/?kbid=321360 2118 * 2119 * For some reason it's not possible to search on primarygrouptoken=XXX 2120 * If someone can show otherwise, I'd like to know about it :) 2121 * this way is resource intensive and generally a pain in the @#%^ 2122 * 2123 * @deprecated deprecated since version 3.1, see get get_primary_group 2124 * @param string $gid Group ID 2125 * @return string 2126 */ 2127 protected function group_cn($gid){ 2128 if ($gid===NULL){ return (false); } 2129 $r=false; 2130 2131 $filter="(&(objectCategory=group)(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP ."))"; 2132 $fields=array("primarygrouptoken","samaccountname","distinguishedname"); 2133 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 2134 $entries = ldap_get_entries($this->_conn, $sr); 2135 2136 for ($i=0; $i<$entries["count"]; $i++){ 2137 if ($entries[$i]["primarygrouptoken"][0]==$gid){ 2138 $r=$entries[$i]["distinguishedname"][0]; 2139 $i=$entries["count"]; 2140 } 2141 } 2142 2143 return ($r); 2144 } 2145 2146 /** 2147 * Coping with AD not returning the primary group 2148 * http://support.microsoft.com/?kbid=321360 2149 * 2150 * This is a re-write based on code submitted by Bruce which prevents the 2151 * need to search each security group to find the true primary group 2152 * 2153 * @param string $gid Group ID 2154 * @param string $usersid User's Object SID 2155 * @return string 2156 */ 2157 protected function get_primary_group($gid, $usersid){ 2158 if ($gid===NULL || $usersid===NULL){ return (false); } 2159 $r=false; 2160 2161 $gsid = substr_replace($usersid,pack('V',$gid),strlen($usersid)-4,4); 2162 $filter='(objectsid='.$this->getTextSID($gsid).')'; 2163 $fields=array("samaccountname","distinguishedname"); 2164 $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); 2165 $entries = ldap_get_entries($this->_conn, $sr); 2166 2167 return $entries[0]['distinguishedname'][0]; 2168 } 2169 2170 /** 2171 * Convert a binary SID to a text SID 2172 * 2173 * @param string $binsid A Binary SID 2174 * @return string 2175 */ 2176 protected function getTextSID($binsid) { 2177 $hex_sid = bin2hex($binsid); 2178 $rev = hexdec(substr($hex_sid, 0, 2)); 2179 $subcount = hexdec(substr($hex_sid, 2, 2)); 2180 $auth = hexdec(substr($hex_sid, 4, 12)); 2181 $result = "$rev-$auth"; 2182 2183 for ($x=0;$x < $subcount; $x++) { 2184 $subauth[$x] = 2185 hexdec($this->little_endian(substr($hex_sid, 16 + ($x * 8), 8))); 2186 $result .= "-" . $subauth[$x]; 2187 } 2188 2189 // Cheat by tacking on the S- 2190 return 'S-' . $result; 2191 } 2192 2193 /** 2194 * Converts a little-endian hex number to one that hexdec() can convert 2195 * 2196 * @param string $hex A hex code 2197 * @return string 2198 */ 2199 protected function little_endian($hex) { 2200 $result = ''; 2201 for ($x = strlen($hex) - 2; $x >= 0; $x = $x - 2) { 2202 $result .= substr($hex, $x, 2); 2203 } 2204 return $result; 2205 } 2206 2207 /** 2208 * Converts a binary attribute to a string 2209 * 2210 * @param string $bin A binary LDAP attribute 2211 * @return string 2212 */ 2213 protected function binary2text($bin) { 2214 $hex_guid = bin2hex($bin); 2215 $hex_guid_to_guid_str = ''; 2216 for($k = 1; $k <= 4; ++$k) { 2217 $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2); 2218 } 2219 $hex_guid_to_guid_str .= '-'; 2220 for($k = 1; $k <= 2; ++$k) { 2221 $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2); 2222 } 2223 $hex_guid_to_guid_str .= '-'; 2224 for($k = 1; $k <= 2; ++$k) { 2225 $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2); 2226 } 2227 $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4); 2228 $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20); 2229 return strtoupper($hex_guid_to_guid_str); 2230 } 2231 2232 /** 2233 * Converts a binary GUID to a string GUID 2234 * 2235 * @param string $binaryGuid The binary GUID attribute to convert 2236 * @return string 2237 */ 2238 public function decodeGuid($binaryGuid) { 2239 if ($binaryGuid === null){ return ("Missing compulsory field [binaryGuid]"); } 2240 2241 $strGUID = $this->binary2text($binaryGuid); 2242 return ($strGUID); 2243 } 2244 2245 /** 2246 * Converts a string GUID to a hexdecimal value so it can be queried 2247 * 2248 * @param string $strGUID A string representation of a GUID 2249 * @return string 2250 */ 2251 protected function strguid2hex($strGUID) { 2252 $strGUID = str_replace('-', '', $strGUID); 2253 2254 $octet_str = '\\' . substr($strGUID, 6, 2); 2255 $octet_str .= '\\' . substr($strGUID, 4, 2); 2256 $octet_str .= '\\' . substr($strGUID, 2, 2); 2257 $octet_str .= '\\' . substr($strGUID, 0, 2); 2258 $octet_str .= '\\' . substr($strGUID, 10, 2); 2259 $octet_str .= '\\' . substr($strGUID, 8, 2); 2260 $octet_str .= '\\' . substr($strGUID, 14, 2); 2261 $octet_str .= '\\' . substr($strGUID, 12, 2); 2262 //$octet_str .= '\\' . substr($strGUID, 16, strlen($strGUID)); 2263 for ($i=16; $i<=(strlen($strGUID)-2); $i++) { 2264 if (($i % 2) == 0) { 2265 $octet_str .= '\\' . substr($strGUID, $i, 2); 2266 } 2267 } 2268 2269 return $octet_str; 2270 } 2271 2272 /** 2273 * Obtain the user's distinguished name based on their userid 2274 * 2275 * 2276 * @param string $username The username 2277 * @param bool $isGUID Is the username passed a GUID or a samAccountName 2278 * @return string 2279 */ 2280 protected function user_dn($username,$isGUID=false){ 2281 $user=$this->user_info($username,array("cn"),$isGUID); 2282 if ($user[0]["dn"]===NULL){ return (false); } 2283 $user_dn=$user[0]["dn"]; 2284 return ($user_dn); 2285 } 2286 2287 /** 2288 * Encode a password for transmission over LDAP 2289 * 2290 * @param string $password The password to encode 2291 * @return string 2292 */ 2293 protected function encode_password($password){ 2294 $password="\"".$password."\""; 2295 $encoded=""; 2296 for ($i=0; $i <strlen($password); $i++){ $encoded.="{$password{$i}}\000"; } 2297 return ($encoded); 2298 } 2299 2300 /** 2301 * Escape strings for the use in LDAP filters 2302 * 2303 * DEVELOPERS SHOULD BE DOING PROPER FILTERING IF THEY'RE ACCEPTING USER INPUT 2304 * Ported from Perl's Net::LDAP::Util escape_filter_value 2305 * 2306 * @param string $str The string the parse 2307 * @author Port by Andreas Gohr <andi@splitbrain.org> 2308 * @return string 2309 */ 2310 protected function ldap_slashes($str){ 2311 return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e', 2312 '"\\\\\".join("",unpack("H2","$1"))', 2313 $str); 2314 } 2315 2316 /** 2317 * Select a random domain controller from your domain controller array 2318 * 2319 * @return string 2320 */ 2321 protected function random_controller(){ 2322 mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions 2323 return ($this->_domain_controllers[array_rand($this->_domain_controllers)]); 2324 } 2325 2326 /** 2327 * Account control options 2328 * 2329 * @param array $options The options to convert to int 2330 * @return int 2331 */ 2332 protected function account_control($options){ 2333 $val=0; 2334 2335 if (is_array($options)){ 2336 if (in_array("SCRIPT",$options)){ $val=$val+1; } 2337 if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; } 2338 if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; } 2339 if (in_array("LOCKOUT",$options)){ $val=$val+16; } 2340 if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; } 2341 //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute. 2342 //For information about how to set the permission programmatically, see the "Property flag descriptions" section. 2343 if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; } 2344 if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; } 2345 if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; } 2346 if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; } 2347 if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; } 2348 if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; } 2349 if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; } 2350 if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; } 2351 if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; } 2352 if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; } 2353 if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; } 2354 if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; } 2355 if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; } 2356 if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; } 2357 if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; } 2358 } 2359 return ($val); 2360 } 2361 2362 /** 2363 * Take an LDAP query and return the nice names, without all the LDAP prefixes (eg. CN, DN) 2364 * 2365 * @param array $groups 2366 * @return array 2367 */ 2368 protected function nice_names($groups){ 2369 2370 $group_array=array(); 2371 for ($i=0; $i<$groups["count"]; $i++){ // For each group 2372 $line=$groups[$i]; 2373 2374 if (strlen($line)>0){ 2375 // More presumptions, they're all prefixed with CN= 2376 // so we ditch the first three characters and the group 2377 // name goes up to the first comma 2378 $bits=explode(",",$line); 2379 $group_array[]=substr($bits[0],3,(strlen($bits[0])-3)); 2380 } 2381 } 2382 return ($group_array); 2383 } 2384 2385 /** 2386 * Delete a distinguished name from Active Directory 2387 * You should never need to call this yourself, just use the wrapper functions user_delete and contact_delete 2388 * 2389 * @param string $dn The distinguished name to delete 2390 * @return bool 2391 */ 2392 protected function dn_delete($dn){ 2393 $result=ldap_delete($this->_conn, $dn); 2394 if ($result!=true){ return (false); } 2395 return (true); 2396 } 2397 2398 /** 2399 * Convert a boolean value to a string 2400 * You should never need to call this yourself 2401 * 2402 * @param bool $bool Boolean value 2403 * @return string 2404 */ 2405 protected function bool2str($bool) { 2406 return ($bool) ? 'TRUE' : 'FALSE'; 2407 } 2408 2409 /** 2410 * Convert 8bit characters e.g. accented characters to UTF8 encoded characters 2411 */ 2412 protected function encode8bit(&$item, $key) { 2413 $encode = false; 2414 if (is_string($item)) { 2415 for ($i=0; $i<strlen($item); $i++) { 2416 if (ord($item[$i]) >> 7) { 2417 $encode = true; 2418 } 2419 } 2420 } 2421 if ($encode === true && $key != 'password') { 2422 $item = utf8_encode($item); 2423 } 2424 } 2425 } 2426 2427 /** 2428 * adLDAP Exception Handler 2429 * 2430 * Exceptions of this type are thrown on bind failure or when SSL is required but not configured 2431 * Example: 2432 * try { 2433 * $adldap = new adLDAP(); 2434 * } 2435 * catch (adLDAPException $e) { 2436 * echo $e; 2437 * exit(); 2438 * } 2439 */ 2440 class adLDAPException extends Exception {} 2441 2442 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Feb 3 03:00:06 2013 | Cross-referenced by PHPXref 0.7 |