PHP Classes

File: contrib/MultiotpAdLdap.php

Recommend this page to a friend!
  Classes of André Liechti   multiOTP PHP class   contrib/MultiotpAdLdap.php   Download  
File: contrib/MultiotpAdLdap.php
Role: Auxiliary script
Content type: text/plain
Description: Auxiliary script
Class: multiOTP PHP class
Authenticate and manage OTP strong user tokens
Author: By
Last change: Update of contrib/MultiotpAdLdap.php
Date: 3 months ago
Size: 79,818 bytes
 

Contents

Class file image Download
<?php /* PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY Version 2.1+ Adapted 2013-2022 by SysCo/al 5.9.5.5 (2023-01-18) * * * 2023-01-18 5.9.5.5 SysCo/al New _ldap_filter attribute * New paging support for 7.3+ * * 2021-04-07 5.8.2.1 SysCo/al Test connection filter changed from (dn=test-connection) to (objectClass=user) * (dn=xxx not supported by eDirectory) * New LDAP type 4 for eDirectory : users are searched with the class posixAccount or user * enhanced LDAP type 2 : users are searched with the class posixAccount or user * * 2018-07-16 5.2.0.0 SysCo/al Active Directory support enhancement (member:1.2.840.113556.1.4.1941:=) * Active Directory primary group optimized detection * _users_dn added * * 2018-02-01 5.1.0.2 SysCo/al using variable $pageSize instead of fixed value * LDAP control paged result called with $iscritical = false * * 2017-12-05 5.0.6.2 SysCo/al expired password option support * * 2017-01-24 5.0.3.3 SysCo/al cache support added * * 2015-08-13 4.3.2.8 SysCo/al ldap_dn_encode (RFC4514) method added * ldap_search_encode (RFC4515) method added * ldap_slashes method calls replaced by ldap_search_encode calls * * 2014-11-04 4.3.0.0 SysCo/al paged result added (if supported by the LDAP PHP library) * more internal attributes added * enhanced SSL support * enhanced debug message * cache added for better results * ldap_get_one_entry_raw method added * ldap_get_entries_raw method added Written by Scott Barnett email: scott@wiggumworld.com http://adldap.sourceforge.net/ Copyright (C) 2006-2007 Scott Barnett I'd appreciate any improvements or additions to be submitted back to benefit the entire community :) Works with PHP 5, should be fine with PHP 4, let me know if/where it doesn't :) Please visit the project website for a full list of the functions and documentation on using them. http://adldap.sourceforge.net/documentation.php This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. ******************************************************************** Something to keep in mind is that Active Directory is a permissions based directory. If you bind as a domain user, you can't fetch as much information on other users as you could as a domain admin. ******************************************************************** Attributes documentation : http://www.selfadsi.org/user-attributes.htm LDAP information, also for other implementation: https://github.com/mfreiholz/iF.SVNAdmin/issues/53 LDAP trick for > 1000: http://php.net/manual/en/function.ldap-search.php */ // Added by SysCo/al if (!defined('PHP_VERSION_ID')) { $version = explode('.', PHP_VERSION); define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); } putenv('LDAPTLS_REQCERT=never'); if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) { define ('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032); } // Different type of accounts in AD define ('ADLDAP_NORMAL_ACCOUNT', 805306368); define ('ADLDAP_WORKSTATION_TRUST', 805306369); define ('ADLDAP_INTERDOMAIN_TRUST', 805306370); define ('ADLDAP_SECURITY_GLOBAL_GROUP', 268435456); define ('ADLDAP_DISTRIBUTION_GROUP', 268435457); define ('ADLDAP_SECURITY_LOCAL_GROUP', 536870912); define ('ADLDAP_DISTRIBUTION_LOCAL_GROUP', 536870913); /* Used to convert the sid to a string (for Active Directory / LDAP purposes) * converts a binary SID into a string representation * @param string $sid * @return string * @link http://blogs.freebsdish.org/tmclaugh/2010/07/21/finding-a-users-primary-group-in-ad/#comment-2855 */ if (!function_exists('sid2str')) { function sid2str($sid) { // The format of a SID binary string is as follows: // 1 byte for the revision level // 1 byte for the number n of variable sub-ids // 6 bytes for identifier authority value // n*4 bytes for n sub-ids // // Example: 010400000000000515000000a681e50e4d6c6c2bca32055f // Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444 $revision = ord($sid[0]); $numberSubID = ord($sid[1]); $subIdStart = 8; // 1 + 1 + 6 $subIdLength = 4; if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) { // Incorrect number of bytes present. return ''; } // 6 bytes = 48 bits can be represented using floats without loss of // precision (see https://gist.github.com/bantu/886ac680b0aef5812f71) $iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', ''); $subIDs = array(); for ($i = 0; $i < $numberSubID; $i++) { $subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength)); $subIDs[] = sprintf('%u', $subID[1]); } // Result for example above: S-1-5-21-249921958-728525901-1594176202 return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs)); } } class MultiotpAdLdap { // BEFORE YOU ASK A QUESTION, PLEASE READ THE DOCUMENTATION AND THE FAQ // http://adldap.sourceforge.net/documentation.php // http://adldap.sourceforge.net/faq.php // You can set your default variables here, or when you invoke the class var $_account_suffix="@mydomain.local"; // Reinitialized to '' by SysCo/al in the constructor var $_base_dn = "DC=mydomain,DC=local"; // Reinitialized to '' by SysCo/al in the constructor // An array of domain controllers. Specify multiple controllers if you // would like the class to balance the LDAP queries amongst multiple servers var $_domain_controllers = array ("dc01.mydomain.local"); // optional account with higher privileges for searching // not really that optional because you can't query much as a user var $_ad_username=NULL; var $_ad_password=NULL; // AD does not return the primary group. http://support.microsoft.com/?kbid=321360 // This tweak will resolve the real primary group, but may be resource intensive. // Setting to false will fudge "Domain Users" and is much faster. Keep in mind though that if // someone's primary group is NOT domain users, this is obviously going to bollocks the results var $_real_primarygroup=true; // Use SSL, your server needs to be setup, please see - http://adldap.sourceforge.net/ldap_ssl.php var $_use_ssl=false; var $_cn_identifier = "samaccountname"; var $_group_cn_identifier = "samaccountname"; var $_group_attribute = "memberof"; // When querying group memberships, do it recursively // eg. User Fred is a member of Group A, which is a member of Group B, which is a member of Group C // user_ingroup("Fred","C") will returns true with this option turned on, false if turned off var $_recursive_groups=true; // You should not need to edit anything below this line //****************************************************************************************** //other variables var $_conn; var $_conn_paged; var $_bind; var $_bind_paged; var $_cache_group_dn; // Added 2014-07-21 by SysCo/al var $_cache_recursive_groups; // Added 2014-07-21 by SysCo/al var $_cache_support; // Added 2014-07-21 by SysCo/al var $_entry_identifier; // Added 2014-07-21 by SysCo/al var $_error; // Added by SysCo/al var $_error_message; // Added by SysCo/al var $_error_no; // Added by SysCo/al var $_ldap_server_type; // Added by SysCo/al, 1 (default) for Active Directory, 2 for Generic LDAP, 3 for legacy Active Directory, 4 for eDirectory (Novell) var $_ldap_filter; // Added by SysCo/al var $_oui_sr; // Added by SysCo/al var $_paging_cookie; // Added 2023 by SysCo/al var $_paging_legacy; // Added 2023 by SysCo/al var $_paging_ldap_control; // Added 2023 by SysCo/al var $_paging_size; // Added 2023 by SysCo/al var $_debug_message; // Added by SysCo/al 4.3.2.2 var $_warning_message; // Added by SysCo/al var $_server_reachable; // Added by SysCo/al var $_cache_folder; // Added by SysCo/al var $_users_dn; // Added by SysCo/al 5.2.0.0 var $_cache_timeout = 3600; // Added by SysCo/al var $_linux_file_mode = '0666'; // Added by SysCo/al // Creation of dynamic property is deprecated var $_oui_paged_sr; var $_expired_password_valid; // default constructor function __construct($options=array()) { $this->_account_suffix = ''; // Added by SysCo/al $this->_base_dn = ''; // Added by SysCo/al $this->_cache_group_dn = array(); // Added 2014-07-21 by SysCo/al $this->_cache_recursive_groups = array(); // Added 2014-07-21 by SysCo/al $this->_cache_support = TRUE; // Added 2014-07-21 by SysCo/al $this->_entry_identifier = array(); // Added 2014-07-21 by SysCo/al $this->_error = TRUE; // Added by SysCo/al $this->_error_message = ''; // Added by SysCo/al $this->_error_no = 0; // Added by SysCo/al $this->_ldap_server_type = 1; // Added by SysCo/al $this->_ldap_filter = ''; // Added by SysCo/al $this->_oui_paged_sr = NULL; // Added by SysCo/al $this->_paging_cookie = ''; $this->_paging_legacy = false; $this->_paging_ldap_control = false; $this->_paging_size = 1000; $this->_debug_message = ''; // Added by SysCo/al $this->_warning_message = ''; // Added by SysCo/al $this->_server_reachable = FALSE; // Added by SysCo/al $this->_cache_folder = ""; // Added by SysCo/al $this->_expired_password_valid = FALSE; // Added by SysCo/al //you can specifically override any of the default configuration options setup above if (count($options)>0){ if (array_key_exists("account_suffix",$options)){ $this->_account_suffix=$options["account_suffix"]; } if (array_key_exists("base_dn",$options)){ $this->_base_dn=$options["base_dn"]; } if (array_key_exists("domain_controllers",$options)){ $this->_domain_controllers=$options["domain_controllers"]; } if (array_key_exists("ad_username",$options)){ $this->_ad_username=$options["ad_username"]; } if (array_key_exists("ad_password",$options)){ $this->_ad_password=$options["ad_password"]; } if (array_key_exists("real_primarygroup",$options)){ $this->_real_primarygroup=$options["real_primarygroup"]; } if (array_key_exists("use_ssl",$options)){ $this->_use_ssl=$options["use_ssl"]; } if (array_key_exists("recursive_groups",$options)){ $this->_recursive_groups=$options["recursive_groups"]; } if (array_key_exists("ldap_server_type",$options)){ $this->_ldap_server_type=$options["ldap_server_type"]; } if (array_key_exists("ldap_filter ",$options)){ $this->_ldap_filter=$options["ldap_filter"]; } if (array_key_exists("cache_support",$options)){ $this->_cache_support=$options["cache_support"]; } if (array_key_exists("cache_folder",$options)){ $this->_cache_folder=$options["cache_folder"]; } if (array_key_exists("expired_password_valid",$options)){ $this->_expired_password_valid=(TRUE == $options["expired_password_valid"]); } if (array_key_exists("users_dn",$options)){ $this->_users_dn=$options["users_dn"]; } // Added by SysCo/al if ($this->_use_ssl) { $ldap_port = 636; } else { $ldap_port = 389; } if (array_key_exists("cn_identifier",$options)){ $this->_cn_identifier=strtolower($options["cn_identifier"]); } if (array_key_exists("group_cn_identifier",$options)){ $this->_group_cn_identifier=strtolower($options["group_cn_identifier"]); } if (array_key_exists("group_attribute",$options)){ $this->_group_attribute=strtolower($options["group_attribute"]); } if (array_key_exists("port",$options)) { $ldap_port = intval($options["port"]); } // if (array_key_exists("time_limit",$options)) : see later, when connection is done } // ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7); $connected = FALSE; // Modified by SysCo/al (check also empty values) if (($this->_ad_username!=NULL) && ($this->_ad_password!=NULL) && ($this->_ad_password!='') && ($this->_ad_username!='')) { //connect to the LDAP server as the username/password // Modified by SysCo/al $count_controllers = count($this->_domain_controllers); foreach($this->_domain_controllers as $dc) { $port = $ldap_port; $controller = $dc; $protocol = "ldap://"; // $dc=$this->random_controller(); if ($this->_use_ssl) { $protocol = "ldaps://"; } $pos = strpos($dc, "://"); if ($pos !== FALSE) { $protocol = substr($dc, 0, $pos+3); $dc = substr($dc, $pos+3); } $pos = strpos($dc, ":"); if ($pos !== FALSE) { $port = substr($dc, $pos+1); $dc = substr($dc, 0, $pos); } if ($this->_conn = ldap_connect($protocol.$dc.":".$port)) { $this->_conn_paged = ldap_connect($protocol.$dc.":".$port); //set some ldap options for talking to AD if (array_key_exists("time_limit",$options)) { ldap_set_option($this->_conn, LDAP_OPT_TIMELIMIT, intval($options["time_limit"])); ldap_set_option($this->_conn_paged, LDAP_OPT_TIMELIMIT, intval($options["time_limit"])); } if ((PHP_VERSION_ID >= 50300) && (array_key_exists("network_timeout",$options))) { ldap_set_option($this->_conn, LDAP_OPT_NETWORK_TIMEOUT, intval($options["network_timeout"])); ldap_set_option($this->_conn_paged, LDAP_OPT_NETWORK_TIMEOUT, intval($options["network_timeout"])); } ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($this->_conn_paged, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 0); ldap_set_option($this->_conn_paged, LDAP_OPT_REFERRALS, 0); // Added 2018-07-12 ldap_set_option($this->_conn, LDAP_OPT_RESTART, 1); ldap_set_option($this->_conn_paged, LDAP_OPT_RESTART, 1); // Added 2023-01-01 $this->_paging_ldap_control = false; // Will be detected further if (PHP_VERSION_ID >= 70300) { $this->_paging_legacy = false; } else { $this->_paging_legacy = true; } //bind as a domain admin if they've set it up $this->_bind = @ldap_bind($this->_conn,$this->_ad_username.$this->_account_suffix,$this->_ad_password); $this->_bind_paged = @ldap_bind($this->_conn_paged,$this->_ad_username.$this->_account_suffix,$this->_ad_password); if ($this->_bind) { if (PHP_VERSION_ID >= 70300) { $ldap_result = ldap_read($this->_conn, '', '(objectClass=*)', ['supportedControl']); if (in_array(LDAP_CONTROL_PAGEDRESULTS, ldap_get_entries($this->_conn, $ldap_result)[0]['supportedcontrol'])) { $this->_paging_ldap_control = true; } else { $this->_paging_ldap_control = false; } } // Check BaseDN access, no paging needed if (FALSE !== (@ldap_search($this->_conn, $this->_base_dn, "(objectClass=user)"))) { $this->_error = FALSE; $this->_error_message = ''; $connected = TRUE; break; } else { $this->_error = TRUE; $this->_error_message = 'FATAL: AD/LDAP bind failed. The BaseDN '.$this->_base_dn.' is not accepted ('.ldap_error($this->_conn).').'; } } else { $this->_server_reachable = (!(-1 == ldap_errno($this->_conn))); if ($this->_use_ssl) { //if you have problems troubleshooting, remove the @ character from the ldap_bind command above to get the actual error message // Modified by SysCo/al $this->_error = TRUE; $this->_error_message = 'FATAL: AD bind failed. Either the LDAPS connection failed or the login credentials are incorrect ('.@ldap_error($this->_conn).').'; } else { // Modified by SysCo/al $this->_error = TRUE; $this->_error_message = 'FATAL: AD bind failed. Check the login credentials ('.ldap_errno($this->_conn).": ".@ldap_error($this->_conn).').'; } if (ldap_get_option($this->_conn, LDAP_OPT_ERROR_STRING, $extended_error)) { if (!empty($extended_error)) { $err_array = explode(',', $extended_error.',,'); $err_array = explode(' ', $err_array[2].' '); $errno = $err_array[2]; if ($this->_expired_password_valid) { /* We accept also "password expired" and "user must reset password" 525 user not found 52e invalid credentials 530 not permitted to logon at this time 531 not permitted to logon at this workstation 532* password expired 533 account disabled 534 The user has not been granted the requested logon type at this machine 701 account expired 773* user must reset password 775 user account locked */ if (('532' == $errno) || ('773' == $errno)) { $this->_error = FALSE; $this->_error_message = 'WARNING: user must reset AD/LDAP password'; $connected = TRUE; break; } } $this->_error_message.= " ($extended_error)"; } } // echo "DEBUG ERROR MESSAGE: ".$this->_error_message; } $this->_error = TRUE; $connected = FALSE; } } } if (!$connected) { $this->_error = TRUE; if ('' == $this->_error_message) { $this->_error_message = 'FATAL: AD connection failed. Check the LDAP/AD controllers ('.@ldap_error($this->_conn).').'; } } else { $this->_error = FALSE; } return ($connected); // But it's the constructor, the result cannot be used } function set_paging_size($paging_size) { $this->_paging_size = $paging_size; } function get_paging_size() { return ($this->_paging_size); } function set_base_dn($base_dn) { $this->_base_dn = $base_dn; } function set_users_dn($users_dn) { $this->_users_dn = $users_dn; } // Added 2016-11-22 by SysCo/al function set_cache_folder($folder) { $this->_cache_folder = $folder; } function get_cache_folder() { return (trim($this->_cache_folder)); } function disable_cache_support() { $this->_cache_support = FALSE; } function enable_cache_support() { $this->_cache_support = TRUE; } function set_cache_timeout($value) { $this->_cache_timeout = intval($value); } function get_cache_timeout() { return intval($this->_cache_timeout); } // Added 2015-06-07 by SysCo/al function get_debug_message() { return trim($this->_debug_message); } // Added 2018-07-10 by SysCo/al function clear_warning_message() { $this->_warning_message = ""; } // Added 2014-07-21 by SysCo/al function get_warning_message() { return trim($this->_warning_message); clear_warning_message(); } // Added 2014-07-21 by SysCo/al function ldap_get_one_entry_raw($id = "GENERIC", $first = FALSE, $srch_id = FALSE, $link_identifier = FALSE) { $linkIdentifier = ((FALSE !== $link_identifier) ? $link_identifier : $this->_conn_paged); $rawData = FALSE; if ($srch_id) { if ($first) { $this->_entry_identifier[$id] = ldap_first_entry($linkIdentifier, $srch_id); } elseif (FALSE !== $this->_entry_identifier[$id]) { $this->_entry_identifier[$id] = ldap_next_entry($linkIdentifier, $this->_entry_identifier[$id]); } if (FALSE !== $this->_entry_identifier[$id]) { $rawData = array(); $rawData['count'] = 0; // To be compatible with the old data organisation (counter at the beginning) $attributes = ldap_get_attributes($linkIdentifier, $this->_entry_identifier[$id]); $distinguishedname_in_attributes = FALSE; for($j=0; $j<$attributes['count']; $j++) { if ('distinguishedname' == strtolower($attributes[$j])) { $distinguishedname_in_attributes = TRUE; } $values = ldap_get_values_len($linkIdentifier, $this->_entry_identifier[$id],$attributes[$j]); $rawData[strtolower($attributes[$j])] = $values; $rawData[strtolower($attributes[$j])]['count'] = (isset($values['count'])?$values['count']:0); } if (!$distinguishedname_in_attributes) { $rawData['distinguishedname'][0] = ldap_get_dn($linkIdentifier, $this->_entry_identifier[$id]); $rawData['distinguishedname']['count'] = 1; $attributes['count']++; } $rawData['count'] = $attributes['count']; } } return $rawData; } // Added by SysCo/al // New implementation 2014-07-21 by SysCo/al function ldap_get_entries_raw($srch_id, $id = "ALL-IN-ONE-LOOP") { $rawData = array(); $rawData['count'] = 0; // To be compatible with the old data organisation (counter at the beginning) $i = 0; if ($result = $this->ldap_get_one_entry_raw($id, TRUE, $srch_id)) { do { $rawData[$i] = $result; $i++; } while ($result = $this->ldap_get_one_entry_raw($id)); } unset($this->_entry_identifier[$id]); $rawData['count'] = $i; // and not count($rawData) because of the ['count'] argument return $rawData; } // Added by SysCo/al function IsError() { return $this->_error; } // Added by SysCo/al function ErrorMessage() { return ($this->IsError()?($this->_error_message):''); } // Added by SysCo/al function IsServerReachable() { return $this->_server_reachable; } // default destructor // Test added by SysCo/al function __destruct(){ if ($this->_conn) { ldap_close ($this->_conn); } if ($this->_conn_paged) { ldap_close ($this->_conn_paged); } } //validate a users login credentials function authenticate($username,$password,$prevent_rebind=false){ if ($username==NULL || $password==NULL){ return (false); } //prevent null binding //bind as the user $this->_bind = @ldap_bind($this->_conn,$username.$this->_account_suffix,$password); $this->_bind_paged = @ldap_bind($this->_conn_paged,$username.$this->_account_suffix,$password); if (!$this->_bind){ return (false); } //once we've checked their details, kick back into admin mode if we have it if ($this->_ad_username!=NULL && !$prevent_rebind){ $this->_bind = @ldap_bind($this->_conn,$this->_ad_username.$this->_account_suffix,$this->_ad_password); $this->_bind_paged = @ldap_bind($this->_conn_paged,$this->_ad_username.$this->_account_suffix,$this->_ad_password); if (!$this->_bind){ // Modified by SysCo/al $this->_error = TRUE; $this->_error_message = 'FATAL: AD rebind failed.'; exit(); } //this should never happen in theory } return (true); } //***************************************************************************************************************** // GROUP FUNCTIONS // Add a group to a group function group_add_group($parent,$child){ //find the parent group's dn $parent_group=$this->group_info($parent,array("cn")); if ($parent_group[0]["dn"]==NULL){ return (false); } $parent_dn=$parent_group[0]["dn"]; //find the child group's dn $child_group=$this->group_info($child,array("cn")); if ($child_group[0]["dn"]==NULL){ return (false); } $child_dn=$child_group[0]["dn"]; $add=array(); $add["member"] = $child_dn; $result=@ldap_mod_add($this->_conn,$parent_dn,$add); if ($result==false){ return (false); } return (true); } // Add a user to a group function group_add_user($group,$user){ //adding a user is a bit fiddly, we need to get the full DN of the user //and add it using the full DN of the group //find the user's dn $user_info=$this->user_info($user,array("cn")); if ($user_info[0]["dn"]==NULL){ return (false); } $user_dn=$user_info[0]["dn"]; //find the group's dn $group_info=$this->group_info($group,array("cn")); if ($group_info[0]["dn"]==NULL){ return (false); } $group_dn=$group_info[0]["dn"]; $add=array(); $add["member"] = $user_dn; $result=@ldap_mod_add($this->_conn,$group_dn,$add); if ($result==false){ return (false); } return (true); } // Create a group function group_create($attributes){ if (!is_array($attributes)){ return ("Attributes must be an array"); } if (!array_key_exists("group_name",$attributes)){ return ("Missing compulsory field [group_name]"); } if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } if (!array_key_exists("description",$attributes)){ return ("Missing compulsory field [description]"); } if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } $attributes["container"]=array_reverse($attributes["container"]); //$member_array = array(); //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; //$member_array[1] = "cn=administrator,cn=Users,dc=yourdomain,dc=com"; $add=array(); $add["cn"] = $attributes["group_name"]; $add[$this->_group_cn_identifier] = $attributes["group_name"]; $add["objectClass"] = "Group"; $add["description"] = $attributes["description"]; //$add["member"] = $member_array; UNTESTED $container="OU=".implode(",OU=",$attributes["container"]); $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add); if ($result!=true){ return (false); } return (true); } // Remove a group from a group function group_del_group($parent,$child){ //find the parent dn $parent_group=$this->group_info($parent,array("cn")); if ($parent_group[0]["dn"]==NULL){ return (false); } $parent_dn=$parent_group[0]["dn"]; //find the child dn $child_group=$this->group_info($child,array("cn")); if ($child_group[0]["dn"]==NULL){ return (false); } $child_dn=$child_group[0]["dn"]; $del=array(); $del["member"] = $child_dn; $result=@ldap_mod_del($this->_conn,$parent_dn,$del); if ($result==false){ return (false); } return (true); } // Remove a user from a group function group_del_user($group,$user){ //find the parent dn $group_info=$this->group_info($group,array("cn")); if ($group_info[0]["dn"]==NULL){ return (false); } $group_dn=$group_info[0]["dn"]; //find the child dn $user_info=$this->user_info($user,array("cn")); if ($user_info[0]["dn"]==NULL){ return (false); } $user_dn=$user_info[0]["dn"]; $del=array(); $del["member"] = $user_dn; $result=@ldap_mod_del($this->_conn,$group_dn,$del); if ($result==false){ return (false); } return (true); } // Returns an array of information for a specified group function group_info( $group_name, $fields=NULL ) { if ($group_name==NULL) { return (false); } if (!$this->_bind) { return (false); } $filter="(&(objectCategory=group)(".$this->_group_cn_identifier."=".$this->ldap_search_encode($group_name)."))"; if ($fields==NULL) { $fields=array("member",$this->_group_attribute,"cn","description","distinguishedname","objectcategory",$this->_group_cn_identifier); } // Search info for a specific group, no paging needed $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields); if (FALSE === $sr) { $this->_warning_message = "group_info: ldap_search error ".ldap_errno($this->_conn).": ".ldap_error($this->_conn); // echo "DEBUG: ".$this->_warning_message."\n"; } $entries = $this->ldap_get_entries_raw($sr); // DEBUG if (0 == count($entries)) { $this->_warning_message = "group_info: No entry for the specified filter $filter"; // echo "DEBUG: ".$this->_warning_message."\n"; } return ($entries); } // Return a complete list of "groups in groups" // Cache added 2014-07-21 by SysCo/al function recursive_groups($group, $cache_only = FALSE) { $this->_debug_message = ""; $this->_warning_message = ""; if ($group==NULL){ return (false); } $ret_groups=array(); $ret_data = FALSE; // echo "DEBUG GROUP is group $group in cache ?\n"; // print_r($this->_cache_recursive_groups); clearstatcache(); if ($this->_cache_support) { if ("" != $this->get_cache_folder()) { if (file_exists($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache")) { if ((filemtime($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache") + $this->get_cache_timeout()) > time()) { if ($cache_handle = fopen($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache", "rb")) { $ret_groups = unserialize(fgets($cache_handle)); $ret_data = TRUE; fclose($cache_handle); @touch($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache"); } } } } elseif (isset($this->_cache_recursive_groups[$group])) { $ret_groups = $this->_cache_recursive_groups[$group]; $ret_data = TRUE; } } if (!$ret_data) { if (!$cache_only) { $groups=$this->group_info($group,array($this->_group_attribute)); // Additional test by SysCo/al if (isset($groups[0][$this->_group_attribute])) { $groups=$groups[0][$this->_group_attribute]; if ($groups){ $group_names=$this->nice_names($groups); $ret_groups=array_merge($ret_groups,$group_names); //final groups to return foreach ($group_names as $id => $group_name){ $child_groups=$this->recursive_groups($group_name); $ret_groups=array_merge($ret_groups,$child_groups); } } } if ($this->_cache_support) { if ("" != $this->get_cache_folder()) { if (file_exists($this->get_cache_folder())) { if ($cache_handle = fopen($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache", "wb")) { fwrite($cache_handle, serialize($ret_groups)); fclose($cache_handle); @chmod($this->get_cache_folder()."ldap_rgroup_".md5($group).".cache", octdec($this->_linux_file_mode)); } } } else { $this->_cache_recursive_groups[$group] = $ret_groups; } } } else { $this->_debug_message = "The requested group $group is not in cache."; } } return ($ret_groups); } //***************************************************************************************************************** // USER FUNCTIONS //create a user function user_create($attributes){ //check for compulsory fields if (!array_key_exists("username",$attributes)){ return ("Missing compulsory field [username]"); } if (!array_key_exists("firstname",$attributes)){ return ("Missing compulsory field [firstname]"); } if (!array_key_exists("surname",$attributes)){ return ("Missing compulsory field [surname]"); } if (!array_key_exists("email",$attributes)){ return ("Missing compulsory field [email]"); } if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } if (array_key_exists("password",$attributes) && !$this->_use_ssl) { // Modified by SysCo/al $this->_error = TRUE; $this->_error_message = 'FATAL: SSL must be configured on your webserver and enabled in the class to set passwords.'; exit(); } if (!array_key_exists("display_name",$attributes)) { $attributes["display_name"]=$attributes["firstname"]." ".$attributes["surname"]; } //translate the schema $add = $this->adldap_schema($attributes); //additional stuff only used for adding accounts $add["cn"][0]=$attributes["display_name"]; $add[$this->_cn_identifier][0]=$attributes["username"]; $add["objectclass"][0]="top"; $add["objectclass"][1]="person"; $add["objectclass"][2]="organizationalPerson"; $add["objectclass"][3]="user"; //person? //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; //set the account control attribute $control_options=array("NORMAL_ACCOUNT"); if (!$attributes["enabled"]){ $control_options[]="ACCOUNTDISABLE"; } $add["userAccountControl"][0]=$this->account_control($control_options); //echo ("<pre>"); print_r($add); //determine the container $attributes["container"]=array_reverse($attributes["container"]); $container="OU=".implode(",OU=",$attributes["container"]); //add the entry $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add); if ($result!=true){ return (false); } return (true); } // group_users($group_name) // Returns an array of users that are members of a group function group_users( $group_name=NUL ) { $result = array(); if ($group_name==NULL) { return (false); } if (!$this->_bind) { return (false); } if ((2 == $this->_ldap_server_type) || (4 == $this->_ldap_server_type)) { // Generic LDAP or eDirectory $filter = "(&(|(objectClass=posixGroup)(objectClass=groupofNames))(".$this->_group_cn_identifier."=".$this->ldap_search_encode($group_name)."))"; } else { // Active Directory $group_account_type = "(|(samaccounttype=".ADLDAP_SECURITY_LOCAL_GROUP.")(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP."))"; $filter = "(&(objectCategory=group)".$group_account_type."(".$this->_group_cn_identifier."=".$this->ldap_search_encode($group_name)."))"; } $fields = array("member", "memberuid"); $paging_cookie = ''; // New paging support for PHP 7.3+, paging loop not implemented yet if ($this->_paging_ldap_control) { $sr = ldap_search($this->_conn, $this->_base_dn, $filter, $fields, 0, 0, 0, LDAP_DEREF_NEVER, [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => ['size' => $this->_paging_size, 'cookie' => $paging_cookie]]]); ldap_parse_result($this->_conn, $sr, $error_code , $matched_dn , $error_message , $referrals, $controls); if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) { $paging_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie']; } else { $paging_cookie = ''; } } else { $sr = ldap_search($this->_conn, $this->_base_dn, $filter, $fields); } if (FALSE === $sr) { $this->_warning_message = "group_users: ldap_search error ".ldap_errno($this->_conn).": ".ldap_error($this->_conn); // echo "DEBUG: ".$this->_warning_message."\n"; } $entries = $this->ldap_get_entries_raw($sr); if (0 == count($entries)) { $this->_warning_message = "group_users: No entry for the specified filter $filter"; // echo "DEBUG: ".$this->_warning_message; } if (isset($entries[0]["member"][0])) { $result = $this->nice_names($entries[0]["member"]); /* for ($i=0; $i++; $i < $entries[0]["member"][count]) { $result[] == ($entries[0]["member"][$i]); } */ } elseif (isset($entries[0]["memberuid"][0])) { $result = $this->nice_names($entries[0]["memberuid"]); } else { $result = array(); } return ($result); } // user_groups($user) // Returns an array of groups that a user is a member off function user_groups($username,$recursive=NULL){ if ($username==NULL){ return (false); } if ($recursive==NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it if (!$this->_bind){ return (false); } //search the directory for their information $info=@$this->user_info($username,array($this->_group_attribute,"member","primarygroupid")); $groups=$this->nice_names($info[0][$this->_group_attribute]); //presuming the entry returned is our guy (unique usernames) if ($recursive){ foreach ($groups as $id => $group_name){ $extra_groups=$this->recursive_groups($group_name); $groups=array_merge($groups,$extra_groups); } } return ($groups); } function rCountRemover($arr) { foreach($arr as $key=>$val) { # (int)0 == "count", so we need to use === if($key === "count") unset($arr[$key]); elseif(is_array($val)) $arr[$key] = $this->rCountRemover($arr[$key]); } return $arr; } // New function, for enhanced Active Directory function user_all_groups($username, $groups_filtering = '') { if ($username==NULL){ return (false); } if (!$this->_bind){ return (false); } $filter = "(&(objectCategory=group)(member:1.2.840.113556.1.4.1941:=".$username.")".$groups_filtering.")"; // $fields = array($this->_group_cn_identifier,$this->_group_attribute,"distinguishedname"); $fields = array("cn"); // Search all groups of one user, it should not reach the paging size, no paging implemented $sr = ldap_search($this->_conn, $this->_base_dn, $filter, $fields); if (FALSE === $sr) { $this->_warning_message = "user_all_groups: ldap_search error ".ldap_errno($this->_conn).": ".ldap_error($this->_conn); // echo "DEBUG: ".$this->_warning_message."\n"; } $group_entries = $this->rCountRemover(ldap_get_entries($this->_conn, $sr)); // echo "DEBUG: info group_entries\n"; // print_r($group_entries); $group_array = array(); foreach ($group_entries as $group_entry) { $group_array[] = $group_entry['cn'][0]; } // echo "DEBUG: group_array\n"; // print_r($group_array); return ($group_array); } // Added by SysCo/al // New implementation 2014-07-21 by SysCo/al // Returns an array of information for filtered users function users_info($username=NULL, $fields=NULL, $groups_filtering = '') { $entries = array(); $entries['count'] = 0; // To be compatible with the old data organisation (counter at the beginning) $i = 0; if ($result = $this->one_user_info(TRUE, $username, $fields, FALSE, $groups_filtering)) { do { $entries[$i] = $result; $i++; } while ($result = $this->one_user_info()); } $entries['count'] = $i; // and not count($entries) because of the ['count'] argument return ($entries); } // Added 2014-07-21 by SysCo/al, enhanced 2018-07-12 and 2023-01-02 function one_user_info( $first = FALSE, $username = NULL, $fields = NULL, $group_cn_cache_only = FALSE, $groups_filtering = '', $in_groups_filtering = array() ) { $this->_warning_message = ''; $sr = FALSE; if ($first) { if ($username==NULL){ return (false); } if (!$this->_bind_paged){ return (false); } if ('' == $this->_users_dn) { $this->_users_dn = $this->_base_dn; } if (1 == $this->_ldap_server_type) { // Active Directory $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(".$this->_cn_identifier."=".$username.")".$groups_filtering.")"; if ($fields==NULL){ $fields=array($this->_cn_identifier,"mail",$this->_group_attribute,"department","description","displayname","telephonenumber","primarygroupid","distinguishedname"); } } elseif (3 == $this->_ldap_server_type) { // legacy Active Directory $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(".$this->_cn_identifier."=".$username."))"; if ($fields==NULL){ $fields=array($this->_cn_identifier,"mail",$this->_group_attribute,"department","description","displayname","telephonenumber","primarygroupid","distinguishedname"); } } else { // eDirectory (4) or Generic LDAP (2) or others $filter = "(&(|(objectClass=posixAccount)(objectClass=user))(".$this->_cn_identifier."=".$username."))"; if ($fields==NULL){ $fields=array($this->_cn_identifier,"mail",$this->_group_attribute,"department","gecos","description","displayname","telephonenumber","gidnumber","distinguishedname"); } } if ("" != $this->_ldap_filter) { $filter = $this->_ldap_filter; $filter = str_replace('{cn_identifier}', $this->_cn_identifier, $filter); $filter = str_replace('{username}', $username, $filter); $filter = str_replace('{groups_filtering}', $groups_filtering, $filter); } // New paging support for PHP 7.3+, old and new paging implemented outside the function if ($this->_paging_ldap_control) { $this->_oui_paged_sr = @ldap_search($this->_conn_paged, $this->_users_dn, $filter, $fields, 0, 0, 0, LDAP_DEREF_NEVER, [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => ['size' => $this->_paging_size, 'cookie' => $this->_paging_cookie]]]); ldap_parse_result($this->_conn_paged, $this->_oui_paged_sr, $error_code , $matched_dn , $error_message , $referrals, $controls); if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) { $this->_paging_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie']; } else { $this->_paging_cookie = ''; } } else { $this->_oui_paged_sr = @ldap_search($this->_conn_paged, $this->_users_dn, $filter, $fields); } if (4 == ldap_errno($this->_conn_paged)) { // 4 = LDAP_SIZELIMIT_EXCEEDED $cr = @ldap_count_entries($this->_conn_paged, $this->_oui_paged_sr); $this->_warning_message = "LDAP server cannot return more than $cr records."; } // echo "DEBUG: Error: ".ldap_errno($this->_conn_paged); // $cr = ldap_count_entries($this->_conn_paged,$sr); // echo $cr; // echo "DEBUG: Error: ".ldap_errno($this->_conn_paged); } if ($one_entry = $this->ldap_get_one_entry_raw("ONE_USER", $first, $this->_oui_paged_sr, $this->_conn_paged)) { // AD does not return the primary group in the ldap query, we may need to fudge it // SysCo/al added a test to check if $entries[0]["primarygroupid"][0] exists $add_primary_group = FALSE; if ($this->_real_primarygroup) { if (isset($one_entry["primarygroupid"][0])) { $pri_grp_rid = $one_entry["primarygroupid"][0]; if (isset($this->_cache_group_dn[$pri_grp_rid])) { $dn_group = $this->_cache_group_dn[$pri_grp_rid]; $one_entry[$this->_group_attribute][] = $dn_group; $add_primary_group = TRUE; } else { $r = ldap_read($this->_conn, $this->_base_dn, "(objectclass=*)", array("objectSid")) or exit(); $data = ldap_get_entries($this->_conn, $r); if (isset($data[0]["objectsid"][0])) { $domain_sid = $data[0]["objectsid"][0]; $domain_sid_s = sid2str($domain_sid); // No paging implemented here, it's a specific object, no paging needed $r = ldap_search($this->_conn, $this->_base_dn, "objectSid=".$domain_sid_s."-".$pri_grp_rid, array("distinguishedname")) or exit(); $data = ldap_get_entries($this->_conn, $r); if (isset($data[0]["distinguishedname"][0])) { $dn_group = $data[0]["distinguishedname"][0]; $one_entry[$this->_group_attribute][] = $dn_group; $add_primary_group = TRUE; if (count($this->_cache_group_dn) < 1000) { // Don't overload the memory cache $this->_cache_group_dn[$pri_grp_rid] = $dn_group; } } } } } } else { $one_entry[$this->_group_attribute][]="CN=Domain Users,CN=Users,".$this->_base_dn; $add_primary_group = TRUE; } if ($add_primary_group) { @$one_entry[$this->_group_attribute]["count"]++; } if ((1 == $this->_ldap_server_type) && ( '' != $groups_filtering)) { // Active Directory (but not legacy) // Add the nested groups of the user $filter = "(&(objectCategory=group)(member:1.2.840.113556.1.4.1941:=".$one_entry[$this->_cn_identifier][0].")".$groups_filtering.")"; $internal_fields = array("cn"); $group_array = array(); // SysCo/al added ldap_search error handling, looking for a specific info, no paging needed if (FALSE !== ($sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $internal_fields))) { $group_entries = $this->rCountRemover(ldap_get_entries($this->_conn, $sr)); foreach ($group_entries as $group_entry) { $group_array[] = $group_entry['cn'][0]; } } foreach($group_array as $one_group) { $add_it = TRUE; foreach($this->nice_names($one_entry[$this->_group_attribute]) as $level_one_group) { if (strpos($one_group, $level_one_group) !== FALSE) { $add_it = FALSE; } } if ($add_it) { $one_entry[$this->_group_attribute][] = $one_group; @$one_entry[$this->_group_attribute]["count"]++; } } // Adding all implicit groups if needed foreach($in_groups_filtering as $one_group_filtering) { $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(".$this->_cn_identifier."=".$one_entry[$this->_cn_identifier][0].")".$one_group_filtering['distinguishedname'].")"; $internal_fields = array("cn"); // SysCo/al added ldap_search error handling, no paging needed if (FALSE !== ($sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $internal_fields))) { if (ldap_count_entries($this->_conn, $sr) > 0) { $add_it = TRUE; foreach($this->nice_names($one_entry[$this->_group_attribute]) as $level_one_group) { if (strpos($one_group_filtering['name'], $level_one_group) !== FALSE) { $add_it = FALSE; } } if ($add_it) { $one_entry[$this->_group_attribute][] = $one_group_filtering['name']; @$one_entry[$this->_group_attribute]["count"]++; } } } } } } return ($one_entry); } // Returns an array of information for a specific user function user_info($username,$fields=NULL){ if ($username==NULL){ return (false); } if (!$this->_bind){ return (false); } if ('' == $this->_users_dn) { $this->_users_dn = $this->_base_dn; } $filter = "(&(".$this->_cn_identifier."=".$username."))"; if ($fields==NULL){ $fields=array($this->_cn_identifier,"mail",$this->_group_attribute,"department","description","displayname","gecos","telephonenumber","primarygroupid"); } // No paging implemented here, it's a specific object, no paging needed $sr=ldap_search($this->_conn, $this->_users_dn, $filter, $fields); $entries = $this->ldap_get_entries_raw($sr); // AD does not return the primary group in the ldap query, we may need to fudge it // SysCo/al added a test to check if $entries[0]["primarygroupid"][0] exists $add_primary_group = FALSE; if ($this->_real_primarygroup){ if (isset($one_entry["primarygroupid"][0])) { $pri_grp_rid = $one_entry["primarygroupid"][0]; if (isset($this->_cache_group_dn[$pri_grp_rid])) { $dn_group = $this->_cache_group_dn[$pri_grp_rid]; $one_entry[$this->_group_attribute][] = $dn_group; $add_primary_group = TRUE; } else { $r = ldap_read($this->_conn, $this->_base_dn, "(objectclass=*)", array("objectSid")) or exit(); $data = ldap_get_entries($this->_conn, $r); if (isset($data[0]["objectsid"][0])) { $domain_sid = $data[0]["objectsid"][0]; $domain_sid_s = sid2str($domain_sid); // No paging implemented here, it's a specific object, no paging needed $r = ldap_search($this->_conn, $this->_base_dn, "objectSid=".$domain_sid_s."-".$pri_grp_rid, array("distinguishedname")) or exit(); $data = ldap_get_entries($this->_conn, $r); if (isset($data[0]["distinguishedname"][0])) { $dn_group = $data[0]["distinguishedname"][0]; $one_entry[$this->_group_attribute][] = $dn_group; $add_primary_group = TRUE; if (count($this->_cache_group_dn) < 1000) { // Don't overload the memory cache $this->_cache_group_dn[$pri_grp_rid] = $dn_group; } } } } } } else { $entries[0][$this->_group_attribute][]="CN=Domain Users,CN=Users,".$this->_base_dn; $add_primary_group = TRUE; } if ($add_primary_group) { @$entries[0][$this->_group_attribute]["count"]++; } return ($entries); } // Returns true if the user is a member of the group function user_ingroup($username,$group,$recursive=NULL){ if ($username==NULL){ return (false); } if ($group==NULL){ return (false); } if (!$this->_bind){ return (false); } if ($recursive==NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it //get a list of the groups $groups=$this->user_groups($username,array($this->_group_attribute),$recursive); //return true if the specified group is in the group list if (in_array($group,$groups)){ return (true); } return (false); } //modify a user function user_modify($username,$attributes){ if ($username==NULL){ return ("Missing compulsory field [username]"); } if (array_key_exists("password",$attributes) && !$this->_use_ssl){ echo ("FATAL: SSL must be configured on your webserver and enabled in the class to set passwords."); exit(); } //if (array_key_exists("container",$attributes)){ //if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } //$attributes["container"]=array_reverse($attributes["container"]); //} //find the dn of the user $user=$this->user_info($username,array("cn")); if ($user[0]["dn"]==NULL){ return (false); } $user_dn=$user[0]["dn"]; //translate the update to the LDAP schema $mod=$this->adldap_schema($attributes); if (!$mod){ return (false); } //set the account control attribute (only if specified) if (array_key_exists("enabled",$attributes)){ if ($attributes["enabled"]){ $control_options=array("NORMAL_ACCOUNT"); } else { $control_options=array("NORMAL_ACCOUNT","ACCOUNTDISABLE"); } $mod["userAccountControl"][0]=$this->account_control($control_options); } //do the update $result=ldap_modify($this->_conn,$user_dn,$mod); if ($result==false){ return (false); } return (true); } // Set the password of a user function user_password($username,$password) { if ($username==NULL){ return (false); } if ($password==NULL){ return (false); } if (!$this->_bind){ return (false); } if (!$this->_use_ssl){ echo ("FATAL: SSL must be configured on your webserver and enabled in the class to set passwords."); exit(); } $user=$this->user_info($username,array("cn")); if ($user[0]["dn"]==NULL){ return (false); } $user_dn=$user[0]["dn"]; $add=array(); $add["unicodePwd"][0]=$this->encode_password($password); $result=ldap_mod_replace($this->_conn,$user_dn,$add); if ($result==false){ return (false); } return (true); } //***************************************************************************************************************** // COMPUTER FUNCTIONS // Returns an array of information for a specific computer function computer_info($computer_name,$fields=NULL) { if ($computer_name==NULL){ return (false); } if (!$this->_bind){ return (false); } $filter="(&(objectClass=computer)(cn=".$computer_name."))"; if ($fields==NULL) { $fields=array($this->_group_attribute,"cn","displayname","dnshostname","distinguishedname","objectcategory","operatingsystem","operatingsystemservicepack","operatingsystemversion"); } // No paging implemented here, it's a specific object, no paging needed $sr = ldap_search($this->_conn,$this->_base_dn,$filter,$fields); $entries = $this->ldap_get_entries_raw($sr); return ($entries); } // Returns all AD users function all_users($include_desc = false, $search = "*", $sorted = true) { if (!$this->_bind){ return (false); } //perform the search and grab all their details $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=".$search."))"; $fields = array($this->_cn_identifier,"displayname"); $sr = ldap_search($this->_conn, $this->_base_dn, $filter, $fields); $entries = $this->ldap_get_entries_raw($sr); $users_array = array(); for ($i=0; $i<$entries["count"]; $i++){ if ($include_desc && strlen($entries[$i]["displayname"][0])>0){ $users_array[ $entries[$i][$this->_cn_identifier][0] ] = $entries[$i]["displayname"][0]; } elseif ($include_desc){ $users_array[ $entries[$i][$this->_cn_identifier][0] ] = $entries[$i][$this->_cn_identifier][0]; } else { array_push($users_array, $entries[$i][$this->_cn_identifier][0]); } } if ($sorted){ asort($users_array); } return ($users_array); } // Returns a complete list of the groups in AD // New implementation 2014-07-22 by SysCo/al (with paging support) function all_groups($include_desc = false, $search = "*", $sorted = true, $local_group = FALSE // $local_group switch added by SysCo/al ) { $this->_warning_message = ""; if (!$this->_bind) { return (false); } if ((2 == $this->_ldap_server_type) || (4 == $this->_ldap_server_type)) { // Generic LDAP or eDirectory $filter="(|(objectClass=posixGroup)(objectClass=groupofNames))"; $fields=array($this->_group_cn_identifier,"description"); } else { // Active Directory //perform the search and grab all their details if ($local_group) { $group_account_type = "(|(samaccounttype=".ADLDAP_SECURITY_LOCAL_GROUP.")(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP."))"; } else { $group_account_type = "(samaccounttype=".ADLDAP_SECURITY_GLOBAL_GROUP.")"; } $filter = "(&(objectCategory=group)".$group_account_type."(cn=".$search."))"; $fields = array($this->_group_cn_identifier,"description"); } $groups_array = array(); $paging_cookie = ''; do { // LDAP pagination loop if ($this->_paging_legacy) { ldap_control_paged_result($this->_conn, $this->_paging_size, false, $paging_cookie); } // New paging support for PHP 7.3+ if ($this->_paging_ldap_control) { $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields, 0, 0, 0, LDAP_DEREF_NEVER, [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => ['size' => $this->_paging_size, 'cookie' => $paging_cookie]]]); ldap_parse_result($this->_conn, $sr, $error_code , $matched_dn , $error_message , $referrals, $controls); if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) { $paging_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie']; } else { $paging_cookie = ''; } } else { $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields); } if (4 == ldap_errno($this->_conn)) { // 4 = LDAP_SIZELIMIT_EXCEEDED $cr = @ldap_count_entries($this->_conn,$sr); $this->_warning_message = "LDAP server cannot return more than $cr records."; } $entries = $this->ldap_get_entries_raw($sr); for ($i=0; $i<$entries["count"]; $i++){ if ($include_desc && strlen($entries[$i]["description"][0]) > 0 ) { $groups_array[ $entries[$i][$this->_group_cn_identifier][0] ] = $entries[$i]["description"][0]; } elseif ($include_desc){ if (isset($entries[$i][$this->_group_cn_identifier][0])) { $groups_array[ $entries[$i][$this->_group_cn_identifier][0] ] = $entries[$i][$this->_group_cn_identifier][0]; } } else { if (isset($entries[$i][$this->_group_cn_identifier][0])) { array_push($groups_array, $entries[$i][$this->_group_cn_identifier][0]); } } } if ($this->_paging_legacy) { ldap_control_paged_result_response($this->_conn, $sr, $paging_cookie); } } while(($paging_cookie !== null) && ($paging_cookie != '')); // LDAP pagination loop if ($this->_paging_legacy) { // Reset LDAP paged result ldap_control_paged_result($this->_conn, $this->_paging_size, false); } if( $sorted ){ asort($groups_array); } return ($groups_array); } //************************************************************************************************************ // UTILITY FUNCTIONS (not intended to be called directly but I suppose you could?) function adldap_schema($attributes) { //ldap doesn't like NULL attributes, only set them if they have values // I'd like to know how to set an LDAP attribute to NULL though, at the moment I set it to a space // SysCo/al added "mobile" $mod = array(); if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; } if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; } //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes? if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; } if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; } if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; } if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; } if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; } if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; } if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; } if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; } if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; } if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; } if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format? if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; } if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; } if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; } if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; } if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; } if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName*** if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; } if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->encode_password($attributes["password"]); } if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; } if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; } if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; } if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; } if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; } if ($attributes["mobile"]){ $mod["telephoneNumber"][0]=$attributes["mobile"]; } if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; } //echo ("<pre>"); print_r($mod); /* // modifying a name is a bit fiddly if ($attributes["firstname"] && $attributes["surname"]){ $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"]; $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"]; $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; } */ if (count($mod)==0) { return (false); } return ($mod); } function group_cn($gid, $cache_only = FALSE, $local_group = FALSE){ // coping with AD not returning the primary group // http://support.microsoft.com/?kbid=321360 // for some reason it's not possible to search on primarygrouptoken=XXX // if someone can show otherwise, I'd like to know about it :) // this way is resource intensive and generally a pain in the @#%^ // Cache added 2014-07-21 by SysCo/al // Cache only added 2014-07-23 by SysCo/al $this->_warning_message = ""; if ($gid==NULL){ return (false); } $r=false; $r_data = FALSE; if ($this->_cache_support) { if ("" != $this->get_cache_folder()) { if (file_exists($this->get_cache_folder()."ldap_cgroup_".md5($gid).".cache")) { if ((filemtime($this->get_cache_folder()."ldap_cgroup_".md5($gid).".cache") + $this->get_cache_timeout()) > time()) { if ($cache_handle = fopen($this->get_cache_folder()."ldap_cgroup_".md5($gid).".cache", "rb")) { $r = unserialize(fgets($cache_handle)); $r_data = TRUE; fclose($cache_handle); touch($this->get_cache_folder()."ldap_group_cn_$gid.cache"); } } } } elseif (isset($this->_cache_group_dn[$gid])) { $r = $this->_cache_group_dn[$gid]; $r_data = TRUE; } } if (!$r_data) { if (!$cache_only) { if ((2 == $this->_ldap_server_type) || (4 == $this->_ldap_server_type)) { // Generic LDAP or eDirectory // http://www.rainingpackets.com/ldap-posixgroup-groupofnames/ $filter="(|(objectClass=posixGroup)(objectClass=groupofNames))"; $fields=array("gidnumber",$this->_group_cn_identifier,"distinguishedname"); } else { // Active Directory or legacy Active Directory if ($local_group) { $group_account_type = "(|(samaccounttype=".ADLDAP_SECURITY_LOCAL_GROUP.")(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP."))"; } else { $group_account_type = "(samaccounttype=".ADLDAP_SECURITY_GLOBAL_GROUP.")"; } $filter="(&(objectCategory=group)".$group_account_type.")"; $fields=array("primarygrouptoken",$this->_group_cn_identifier,"distinguishedname"); } $paging_cookie = ''; do { // LDAP pagination loop if ($this->_paging_legacy) { ldap_control_paged_result($this->_conn, $this->_paging_size, false, $paging_cookie); } $sr = @ldap_search($this->_conn,$this->_base_dn,$filter,$fields); if (4 == ldap_errno($this->_conn)) { // 4 = LDAP_SIZELIMIT_EXCEEDED $cr = @ldap_count_entries($this->_conn,$sr); $this->_warning_message = "LDAP server cannot return more than $cr records."; } $entries = $this->ldap_get_entries_raw($sr); for ($i=0; $i<$entries["count"]; $i++) { // if (!isset($entries[$i]["distinguishedname"][0])) if ((2 == $this->_ldap_server_type) || (4 == $this->_ldap_server_type)) { // Generic LDAP and eDirectory, we don't want the full distinguishedname for posixGroups, cn only // $entries[$i]["distinguishedname"][0] = ldap_get_dn($this->_conn, $entries[$i]); // We want to use the cn only if (isset($entries[$i][$this->_group_cn_identifier][0])) { $entries[$i]["distinguishedname"][0] = $entries[$i][$this->_group_cn_identifier][0]; } } if (!isset($entries[$i]["primarygrouptoken"][0])) { $entries[$i]["primarygrouptoken"][0] = (isset($entries[$i]["gidnumber"][0])?$entries[$i]["gidnumber"][0]:NULL); } if ($this->_cache_support) { if (NULL !== $entries[$i]["primarygrouptoken"][0]) { if ("" != $this->get_cache_folder()) { if (file_exists($this->get_cache_folder())) { if ($cache_handle = fopen($this->get_cache_folder()."ldap_cgroup_".md5($entries[$i]["primarygrouptoken"][0]).".cache", "wb")) { fwrite($cache_handle, serialize($entries[$i]["distinguishedname"][0])); fclose($cache_handle); @chmod($this->get_cache_folder()."ldap_cgroup_".md5($entries[$i]["primarygrouptoken"][0]).".cache", octdec($this->_linux_file_mode)); } } } else { $this->_cache_group_dn[$entries[$i]["primarygrouptoken"][0]] = $entries[$i]["distinguishedname"][0]; } } } if ($entries[$i]["primarygrouptoken"][0]==$gid){ $r=$entries[$i]["distinguishedname"][0]; $i=$entries["count"]; } } if ($this->_paging_legacy) { ldap_control_paged_result_response($this->_conn, $sr, $paging_cookie); } } while(($paging_cookie !== null) && ($paging_cookie != '')); // LDAP pagination loop if ($this->_paging_legacy) { // Reset LDAP paged result ldap_control_paged_result($this->_conn, $this->_paging_size, false); } } } return ($r); } // Encode a password for transmission over LDAP function encode_password($password){ $password="\"".$password."\""; $encoded=""; for ($i=0; $i <strlen($password); $i++){ $encoded.=$password[$i]."\000"; } return ($encoded); } // Escape bad characters // DEVELOPERS SHOULD BE DOING PROPER FILTERING IF THEY'RE ACCEPTING USER INPUT // this is just a list of characters with known problems and I'm trying not to strip out other languages function ldap_slashes($str){ $illegal=array("(",")","#"); // the + character has problems too, but it's an illegal character $legal=array(); foreach ($illegal as $id => $char){ $legal[$id]="\\".$char; } //make up the array of legal chars $str=str_replace($illegal,$legal,$str); //replace them return ($str); } // Added by SysCo/al 2015-08-13 // This method will encode the string using RFC4514 // https://www.ietf.org/rfc/rfc4514.txt // http://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx function ldap_dn_encode($str) { $decoded=array("\\",",","+","<",">",";","\"","="); $encoded=array(); foreach ($decoded as $id => $char) { $encoded[$id]="\\".$char; } $str = str_replace($decoded,$encoded,$str); // Leading and trailing space if (" " == (substr($str,0,1))) { $str = "\\ ".$str; } if (" " == (substr($str,-1))) { $str = substr($str,0,-1)."\\ "; } // Leading # (RFC says leading one only) if ("#" == (substr($str,0,1))) { $str = "\\#".$str; } return ($str); } // Added by SysCo/al 2015-08-13 // This method will decode the string using RFC4514 // https://www.ietf.org/rfc/rfc4514.txt // http://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx function ldap_dn_decode($str) { $decoded=array(",","#","+","<",">",";","\"","="," ","\\"); $encoded=array(); foreach ($decoded as $id => $char) { $encoded[$id]="\\".$char; } $str = str_replace($encoded,$decoded,$str); return ($str); } // Added by SysCo/al 2015-08-13 // This method will encode the string using RFC4515 (and we added the / as stated by Microsoft) // https://www.ietf.org/rfc/rfc4515.txt // https://msdn.microsoft.com/en-us/library/aa746475%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 function ldap_search_encode($str) { $decoded=array("\\","*","(",")",chr(0),"/"); $encoded=array("\\5c","\\2a","\\28","\\29","\\00","\\2f"); $str=str_replace($decoded,$encoded,$str); return ($str); } // Return a random controller function random_controller(){ //select a random domain controller mt_srand(doubleval(microtime()) * 100000000); // for older php versions return ($this->_domain_controllers[array_rand($this->_domain_controllers)]); } function account_control($options){ $val=0; if (is_array($options)){ if (in_array("SCRIPT",$options)){ $val=$val+1; } if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; } if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; } if (in_array("LOCKOUT",$options)){ $val=$val+16; } if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; } //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute. //For information about how to set the permission programmatically, see the "Property flag descriptions" section. if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; } if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; } if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; } if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; } if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; } if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; } if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; } if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; } if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; } if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; } if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; } if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; } if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; } if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; } if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; } } return ($val); } // Take an ldap query and return the nice names, without all the LDAP prefixes (eg. CN, DN) function nice_names($groups){ $group_array=array(); for ($i=0; $i<$groups["count"]; $i++) { //for each group if (isset($groups[$i])) { // Patched by SysCo/al $line=trim($groups[$i]); if (strlen($line)>0){ //more presumptions, they're all prefixed with CN= (but no more yet, patched by SysCo/al //so we ditch the first three characters and the group //name goes up to the first comma $bits=explode(",",$line); if (1== count($bits)) { $group_array[]=$bits[0]; // Patched by SysCo/al } else { $prefix_len=strpos($bits[0], "="); // Patched by SysCo/al to allow also various length (not only 3) if (FALSE === $prefix_len) { $group_array[]=$bits[0]; } else { $group_array[]=substr($bits[0],$prefix_len+1); // Patched by SysCo/al } } } } } return ($group_array); } } ?>