diff --git a/composer.json b/composer.json index 5ee4bab..98d7c14 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "adldap/adldap", + "name": "cupdx/adldap", "type": "library", "description": "adLDAP, PHP LDAP library for manipulating Active Directory", "keywords": [ diff --git a/examples/menu.php b/examples/menu.php index f0fac7c..c8b41e5 100644 --- a/examples/menu.php +++ b/examples/menu.php @@ -1,4 +1,10 @@ main) page. + */ + session_start(); ?> diff --git a/lib/adLDAP/adLDAP.php b/lib/adLDAP/adLDAP.php index f36c3aa..772aeac 100644 --- a/lib/adLDAP/adLDAP.php +++ b/lib/adLDAP/adLDAP.php @@ -1,30 +1,30 @@ ldapConnection) { - return $this->ldapConnection; - } - return false; - } - - /** - * Get the bind status - * - * @return bool - */ - public function getLdapBind() { - return $this->ldapBind; - } - - /** - * Get the current base DN - * - * @return string - */ - public function getBaseDn() { - return $this->baseDn; - } - - /** - * Set the current base DN - * - * @param string $baseDn - */ - public function setBaseDn($baseDn) { - $this->baseDn = $baseDn; - } - - /** - * The group class - * - * @var \adLDAP\classes\adLDAPGroups - */ - protected $groupClass; - - /** - * Get the group class interface - * - * @return \adLDAP\classes\adLDAPGroups - */ - public function group() { - if (!$this->groupClass) { - $this->groupClass = new \adLDAP\classes\adLDAPGroups($this); - } - return $this->groupClass; - } - - /** - * The user class - * - * @var \adLDAP\classes\adLDAPUsers - */ - protected $userClass; - - /** - * Get the userclass interface - * - * @return \adLDAP\classes\adLDAPUsers - */ - public function user() { - if (!$this->userClass) { - $this->userClass = new \adLDAP\classes\adLDAPUsers($this); - } - return $this->userClass; - } - - /** - * The folders class - * - * @var \adLDAP\classes\adLDAPFolders - */ - protected $folderClass; - - /** - * Get the folder class interface - * - * @return \adLDAP\classes\adLDAPFolders - */ - public function folder() { - if (!$this->folderClass) { - $this->folderClass = new \adLDAP\classes\adLDAPFolders($this); - } - return $this->folderClass; - } - - /** - * The utils class - * - * @var \adLDAP\classes\adLDAPUtils - */ - protected $utilClass; - - /** - * Get the utils class interface - * - * @return \adLDAP\classes\adLDAPUtils - */ - public function utilities() { - if (!$this->utilClass) { - $this->utilClass = new \adLDAP\classes\adLDAPUtils($this); - } - return $this->utilClass; - } - - /** - * The contacts class - * - * @var \adLDAP\classes\adLDAPContacts - */ - protected $contactClass; - - /** - * Get the contacts class interface - * - * @return \adLDAP\classes\adLDAPContacts - */ - public function contact() { - if (!$this->contactClass) { - $this->contactClass = new \adLDAP\classes\adLDAPContacts($this); - } - return $this->contactClass; - } - - /** - * The exchange class - * - * @var \adLDAP\classes\adLDAPExchange - */ - protected $exchangeClass; - - /** - * Get the exchange class interface - * - * @return \adLDAP\classes\adLDAPExchange - */ - public function exchange() { - if (!$this->exchangeClass) { - $this->exchangeClass = new \adLDAP\classes\adLDAPExchange($this); - } - return $this->exchangeClass; - } - - /** - * The computers class - * - * @var \adLDAP\classes\adLDAPComputers - */ - protected $computerClass; - - /** - * Get the computers class interface - * - * @return \adLDAP\classes\adLDAPComputers - */ - public function computer() { - if (!$this->computerClass) { - $this->computerClass = new \adLDAP\classes\adLDAPComputers($this); - } - return $this->computerClass; - } - - /** - * Getters and Setters - */ - - /** - * Set the account suffix - * - * @param string $accountSuffix - * @return void - */ - public function setAccountSuffix($accountSuffix) { - $this->accountSuffix = $accountSuffix; - } - - /** - * Get the account suffix - * - * @return string - */ - public function getAccountSuffix() { - return $this->accountSuffix; - } - - /** - * Set the domain controllers array - * - * @param array $domainControllers - * @return void - */ - public function setDomainControllers(array $domainControllers) { - $this->domainControllers = $domainControllers; - } - - /** - * Get the list of domain controllers - * - * @return void - */ - public function getDomainControllers() { - return $this->domainControllers; - } - - /** - * Sets the port number your domain controller communicates over - * - * @param int $adPort - */ - public function setPort($adPort) { - $this->adPort = $adPort; - } - - /** - * Gets the port number your domain controller communicates over - * - * @return int - */ - public function getPort() { - return $this->adPort; - } - - /** - * Set the username of an account with higher priviledges - * - * @param string $adminUsername - * @return void - */ - public function setAdminUsername($adminUsername) { - $this->adminUsername = $adminUsername; - } - - /** - * Get the username of the account with higher priviledges - * - * This will throw an exception for security reasons - */ - public function getAdminUsername() { - throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); - } - - /** - * Set the password of an account with higher priviledges - * - * @param string $adminPassword - * @return void - */ - public function setAdminPassword($adminPassword) { - $this->adminPassword = $adminPassword; - } - - /** - * Get the password of the account with higher priviledges - * - * This will throw an exception for security reasons - */ - public function getAdminPassword() { - throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); - } - - /** - * Set whether to detect the true primary group - * - * @param bool $realPrimaryGroup - * @return void - */ - public function setRealPrimaryGroup($realPrimaryGroup) { - $this->realPrimaryGroup = $realPrimaryGroup; - } - - /** - * Get the real primary group setting - * - * @return bool - */ - public function getRealPrimaryGroup() { - return $this->realPrimaryGroup; - } - - /** - * Set whether to use SSL - * - * @param bool $useSSL - * @return void - */ - public function setUseSSL($useSSL) { - $this->useSSL = $useSSL; - // Set the default port correctly - if($this->useSSL) { - $this->setPort(self::ADLDAP_LDAPS_PORT); - } - else { - $this->setPort(self::ADLDAP_LDAP_PORT); - } - } - - /** - * Get the SSL setting - * - * @return bool - */ - public function getUseSSL() { - return $this->useSSL; - } - - /** - * Set whether to use TLS - * - * @param bool $useTLS - * @return void - */ - public function setUseTLS($useTLS) { - $this->useTLS = $useTLS; - } - - /** - * Get the TLS setting - * - * @return bool - */ - public function getUseTLS() { - return $this->useTLS; - } - - /** - * Set whether to use SSO - * Requires ldap_sasl_bind support. Be sure --with-ldap-sasl is used when configuring PHP otherwise this function will be undefined. - * - * @param bool $useSSO - * @return void - */ - public function setUseSSO($useSSO) { - if ($useSSO === true && !$this->ldapSaslSupported()) { - throw new adLDAPException('No LDAP SASL support for PHP. See: http://www.php.net/ldap_sasl_bind'); - } - $this->useSSO = $useSSO; - } - - /** - * Get the SSO setting - * - * @return bool - */ - public function getUseSSO() { - return $this->useSSO; - } - - /** - * Set whether to lookup recursive groups - * - * @param bool $recursiveGroups - * @return void - */ - public function setRecursiveGroups($recursiveGroups) { - $this->recursiveGroups = $recursiveGroups; - } - - /** - * Get the recursive groups setting - * - * @return bool - */ - public function getRecursiveGroups() { - return $this->recursiveGroups; - } - - /** - * Default Constructor - * - * Tries to bind to the AD domain over LDAP or LDAPs - * - * @param array $options Array of options to pass to the constructor - * @throws \Exception - if unable to bind to Domain Controller - * @return bool - */ - function __construct($options = array()) { - // You can specifically overide any of the default configuration options setup above - if (count($options)>0) { - if (array_key_exists("account_suffix",$options)) { $this->accountSuffix = $options["account_suffix"]; } - if (array_key_exists("base_dn",$options)) { $this->baseDn = $options["base_dn"]; } - if (array_key_exists("domain_controllers",$options)) { - if (!is_array($options["domain_controllers"])) { - throw new adLDAPException('[domain_controllers] option must be an array'); - } - $this->domainControllers = $options["domain_controllers"]; - } - if (array_key_exists("admin_username",$options)) { $this->adminUsername = $options["admin_username"]; } - if (array_key_exists("admin_password",$options)) { $this->adminPassword = $options["admin_password"]; } - if (array_key_exists("real_primarygroup",$options)) { $this->realPrimaryGroup = $options["real_primarygroup"]; } - if (array_key_exists("use_ssl",$options)) { $this->setUseSSL($options["use_ssl"]); } - if (array_key_exists("use_tls",$options)) { $this->useTLS = $options["use_tls"]; } - if (array_key_exists("recursive_groups",$options)) { $this->recursiveGroups = $options["recursive_groups"]; } - if (array_key_exists("follow_referrals", $options)) { $this->followReferrals = $options["follow_referrals"]; } - if (array_key_exists("ad_port",$options)) { $this->setPort($options["ad_port"]); } - if (array_key_exists("sso",$options)) { - $this->setUseSSO($options["sso"]); - if (!$this->ldapSaslSupported()) { - $this->setUseSSO(false); - } - } - } - - if ($this->ldapSupported() === false) { - throw new adLDAPException('No LDAP support for PHP. See: http://www.php.net/ldap'); - } - - return $this->connect(); - } - - /** - * Default Destructor - * - * Closes the LDAP connection - * - * @return void - */ - function __destruct() { - $this->close(); - } - - /** - * Connects and Binds to the Domain Controller - * - * @return bool - */ - public function connect() { - // Connect to the AD/LDAP server as the username/password - $domainController = $this->randomController(); - if ($this->useSSL) { - $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort); - } else { - $this->ldapConnection = ldap_connect("ldap://" . $domainController, $this->adPort); - } - - // Set some ldap options for talking to AD - ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); - ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, $this->followReferrals); - - if ($this->useTLS) { - ldap_start_tls($this->ldapConnection); - } - - // Bind as a domain admin if they've set it up - if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) { - $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword); - if (!$this->ldapBind) { - if ($this->useSSL && !$this->useTLS) { - // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message - throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError()); - } - else { - throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError()); - } - } - } - if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) { - putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']); - $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); - if (!$this->ldapBind) { - throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); - } - else { - return true; - } - } - - if ($this->baseDn == NULL) { - $this->baseDn = $this->findBaseDn(); - } - return true; - } - - /** - * Closes the LDAP connection - * - * @return void - */ - public function close() { - if ($this->ldapConnection) { - @ldap_close($this->ldapConnection); - } - } - - /** - * Validate a user's login credentials - * - * @param string $username A user's AD username - * @param string $password A user's AD password - * @param bool optional $preventRebind - * @return bool - */ - public function authenticate($username, $password, $preventRebind = false) { - // Prevent null binding - if ($username === NULL || $password === NULL) { return false; } - if (empty($username) || empty($password)) { return false; } - - // Allow binding over SSO for Kerberos - if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) { - putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']); - $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); - if (!$this->ldapBind) { - throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); - } - else { - return true; - } - } - - // Bind as the user - $ret = true; - $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password); - if (!$this->ldapBind) { - $ret = false; - } - - // Cnce we've checked their details, kick back into admin mode if we have it - if ($this->adminUsername !== NULL && !$preventRebind) { - $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix , $this->adminPassword); - if (!$this->ldapBind) { - // This should never happen in theory - throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); - } - } - return $ret; - } - - /** - * Return a list of all found objects (except computer) in AD - * $search has to match either cn, displayname, samaccountname or sn - * - * @param bool $includeDescription Return a description,cn, displayname and distinguishedname of the user - * @param string $search Search parameter - * @param bool $sorted Sort the user accounts - * @return array, if $includeDescription=true then a multi-dimensional array - */ - public function search($includeDescription = false, $search = "*", $sorted = true) { - if (!$this->getLdapBind()) { return false; } - - // Perform the search and grab all their details - //$filter = "(|(cn=" . $search . ")(displayname=" . $search . ")(samaccountname=" . $search . "))"; - $filter = "(&(!(objectClass=computer))(|(anr=" . $search . ")))"; - $fields = array("cn","description","displayname","distinguishedname","samaccountname"); - $sr = ldap_search($this->getLdapConnection(), $this->getBaseDn(), $filter, $fields); - $entries = ldap_get_entries($this->getLdapConnection(), $sr); - - $objectArray = array(); - for ($i=0; $i<$entries["count"]; $i++) { - if ($includeDescription && strlen($entries[$i]["description"][0])>0) { - $objectArray[$entries[$i]["samaccountname"][0]] = array($entries[$i]["cn"][0],$entries[$i]["description"][0],$entries[$i]["displayname"][0],$entries[$i]["distinguishedname"][0]); - } elseif ($includeDescription) { - // description is set to displayname if no description is present - $objectArray[$entries[$i]["samaccountname"][0]] = array($entries[$i]["cn"][0],$entries[$i]["displayname"][0],$entries[$i]["displayname"][0],$entries[$i]["distinguishedname"][0]); - } else { - array_push($objectArray, $entries[$i]["samaccountname"][0]); - } - } - if ($sorted) { - asort($objectArray); - } - return $objectArray; - } - - /** - * Returns objectClass in an array - * - * @param string $distinguisedName The full DN of a contact - * @return array - */ - public function getObjectClass($distinguishedName) { - if ($distinguishedName === NULL) { return false; } - if (!$this->getLdapBind()) { return false; } - - $filter = "distinguishedName=" . $this->utilities()->ldapSlashes($distinguishedName); - - $fields = array("objectclass"); - $sr = ldap_search($this->getLdapConnection(), $this->getBaseDn(), $filter, $fields); - $entries = ldap_get_entries($this->getLdapConnection(), $sr); - - $objects = array(); - for ($i=0; $i<$entries[0]["objectclass"]["count"]; $i++) { - array_push($objects, $entries[0]["objectclass"][$i]); - } - return $objects; - } - - /** - * Find the Base DN of your domain controller - * - * @return string - */ - public function findBaseDn() { - $namingContext = $this->getRootDse(array('defaultnamingcontext')); - return $namingContext[0]['defaultnamingcontext'][0]; - } - - /** - * Get the RootDSE properties from a domain controller - * - * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext - * @return array - */ - public function getRootDse($attributes = array("*", "+")) { - if (!$this->ldapBind) { return (false); } - - $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes); - $entries = @ldap_get_entries($this->ldapConnection, $sr); - return $entries; - } - - /** - * Get last error from Active Directory - * - * This function gets the last message from Active Directory - * This may indeed be a 'Success' message but if you get an unknown error - * it might be worth calling this function to see what errors were raised - * - * return string - */ - public function getLastError() { - return @ldap_error($this->ldapConnection); - } - - /** - * Detect LDAP support in php - * - * @return bool - */ - protected function ldapSupported() { - if (!function_exists('ldap_connect')) { - return false; - } - return true; - } - - /** - * Detect ldap_sasl_bind support in PHP - * - * @return bool - */ - protected function ldapSaslSupported() { - if (!function_exists('ldap_sasl_bind')) { - return false; - } - return true; - } - - /** - * Schema - * - * @param array $attributes Attributes to be queried - * @return array - */ - public function adldap_schema($attributes) { - - // LDAP doesn't like NULL attributes, only set them if they have values - // If you wish to remove an attribute you should set it to a space - // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute - $mod=array(); - - // Check every attribute to see if it contains 8bit characters and then UTF8 encode them - array_walk($attributes, array($this, 'encode8bit')); - - if (isset($attributes["address_city"])){ $mod["l"][0]=$attributes["address_city"]; } - if (isset($attributes["address_code"])){ $mod["postalCode"][0]=$attributes["address_code"]; } - //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes? - if (isset($attributes["address_country"])){ $mod["c"][0]=$attributes["address_country"]; } - if (isset($attributes["address_pobox"])){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; } - if (isset($attributes["address_state"])){ $mod["st"][0]=$attributes["address_state"]; } - if (isset($attributes["address_street"])){ $mod["streetAddress"][0]=$attributes["address_street"]; } - if (isset($attributes["company"])){ $mod["company"][0]=$attributes["company"]; } - if (isset($attributes["change_password"])){ $mod["pwdLastSet"][0]=0; } - if (isset($attributes["department"])){ $mod["department"][0]=$attributes["department"]; } - if (isset($attributes["description"])){ $mod["description"][0]=$attributes["description"]; } - if (isset($attributes["display_name"])){ $mod["displayName"][0]=$attributes["display_name"]; } - if (isset($attributes["email"])){ $mod["mail"][0]=$attributes["email"]; } - if (isset($attributes["expires"])){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format? - if (isset($attributes["firstname"])){ $mod["givenName"][0]=$attributes["firstname"]; } - if (isset($attributes["home_directory"])){ $mod["homeDirectory"][0]=$attributes["home_directory"]; } - if (isset($attributes["home_drive"])){ $mod["homeDrive"][0]=$attributes["home_drive"]; } - if (isset($attributes["initials"])){ $mod["initials"][0]=$attributes["initials"]; } - if (isset($attributes["logon_name"])){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; } - if (isset($attributes["manager"])){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName*** - if (isset($attributes["office"])){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; } - if (isset($attributes["password"])){ $mod["unicodePwd"][0]=$this->user()->encodePassword($attributes["password"]); } - if (isset($attributes["profile_path"])){ $mod["profilepath"][0]=$attributes["profile_path"]; } - if (isset($attributes["script_path"])){ $mod["scriptPath"][0]=$attributes["script_path"]; } - if (isset($attributes["surname"])){ $mod["sn"][0]=$attributes["surname"]; } - if (isset($attributes["title"])){ $mod["title"][0]=$attributes["title"]; } - if (isset($attributes["telephone"])){ $mod["telephoneNumber"][0]=$attributes["telephone"]; } - if (isset($attributes["mobile"])){ $mod["mobile"][0]=$attributes["mobile"]; } - if (isset($attributes["pager"])){ $mod["pager"][0]=$attributes["pager"]; } - if (isset($attributes["ipphone"])){ $mod["ipphone"][0]=$attributes["ipphone"]; } - if (isset($attributes["web_page"])){ $mod["wWWHomePage"][0]=$attributes["web_page"]; } - if (isset($attributes["fax"])){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; } - if (isset($attributes["enabled"])){ $mod["userAccountControl"][0]=$attributes["enabled"]; } - if (isset($attributes["homephone"])){ $mod["homephone"][0]=$attributes["homephone"]; } - - // Distribution List specific schema - if (isset($attributes["group_sendpermission"])){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; } - if (isset($attributes["group_rejectpermission"])){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; } - - // Exchange Schema - if (isset($attributes["exchange_homemdb"])){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; } - if (isset($attributes["exchange_mailnickname"])){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; } - if (isset($attributes["exchange_proxyaddress"])){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; } - if (isset($attributes["exchange_usedefaults"])){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; } - if (isset($attributes["exchange_policyexclude"])){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; } - if (isset($attributes["exchange_policyinclude"])){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; } - if (isset($attributes["exchange_addressbook"])){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; } - if (isset($attributes["exchange_altrecipient"])){ $mod["altRecipient"][0]=$attributes["exchange_altrecipient"]; } - if (isset($attributes["exchange_deliverandredirect"])){ $mod["deliverAndRedirect"][0]=$attributes["exchange_deliverandredirect"]; } - - // This schema is designed for contacts - if (isset($attributes["exchange_hidefromlists"])){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; } - if (isset($attributes["contact_email"])){ $mod["targetAddress"][0]=$attributes["contact_email"]; } - - //echo ("
"); 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); - } - - /** - * Convert 8bit characters e.g. accented characters to UTF8 encoded characters - */ - protected function encode8Bit(&$item, $key) { - $encode = false; - if (is_string($item)) { - for ($i=0; $i> 7) { - $encode = true; - } - } - } - if ($encode === true && $key != 'password') { - $item = utf8_encode($item); - } - } - - /** - * Select a random domain controller from your domain controller array - * - * @return string - */ - protected function randomController() { - mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions - /*if (sizeof($this->domainControllers) > 1) { - $adController = $this->domainControllers[array_rand($this->domainControllers)]; - // Test if the controller is responding to pings - $ping = $this->pingController($adController); - if ($ping === false) { - // Find the current key in the domain controllers array - $key = array_search($adController, $this->domainControllers); - // Remove it so that we don't end up in a recursive loop - unset($this->domainControllers[$key]); - // Select a new controller - return $this->randomController(); - } - else { - return ($adController); - } - } */ - return $this->domainControllers[array_rand($this->domainControllers)]; - } - - /** - * Test basic connectivity to controller - * - * @return bool - */ - protected function pingController($host) { - $port = $this->adPort; - fsockopen($host, $port, $errno, $errstr, 10); - if ($errno > 0) { - return false; - } - return true; - } + + /** + * Connection and bind default variables + * + * @var mixed + * @var mixed + */ + protected $ldapConnection; + protected $ldapBind; + + /** + * Get the active LDAP Connection + * + * @return resource|bool + */ + public function getLdapConnection() + { + if ($this->ldapConnection) + { + return $this->ldapConnection; + } + + return false; + } + + /** + * Get the bind status + * + * @return bool + */ + public function getLdapBind() + { + return $this->ldapBind; + } + + /** + * Get the current base DN + * + * @return string + */ + public function getBaseDn() + { + return $this->baseDn; + } + + /** + * Set the current base DN + * + * @param string $baseDn + */ + public function setBaseDn($baseDn) + { + $this->baseDn = $baseDn; + } + + /** + * The group class + * + * @var \adLDAP\classes\adLDAPGroups + */ + protected $groupClass; + + /** + * Get the group class interface + * + * @return \adLDAP\classes\adLDAPGroups + */ + public function group() + { + if (!$this->groupClass) + { + $this->groupClass = new \adLDAP\classes\adLDAPGroups($this); + } + return $this->groupClass; + } + + /** + * The user class + * + * @var \adLDAP\classes\adLDAPUsers + */ + protected $userClass; + + /** + * Get the userclass interface + * + * @return \adLDAP\classes\adLDAPUsers + */ + public function user() + { + if (!$this->userClass) + { + $this->userClass = new \adLDAP\classes\adLDAPUsers($this); + } + return $this->userClass; + } + + /** + * The folders class + * + * @var \adLDAP\classes\adLDAPFolders + */ + protected $folderClass; + + /** + * Get the folder class interface + * + * @return \adLDAP\classes\adLDAPFolders + */ + public function folder() + { + if (!$this->folderClass) + { + $this->folderClass = new \adLDAP\classes\adLDAPFolders($this); + } + return $this->folderClass; + } + + /** + * The utils class + * + * @var \adLDAP\classes\adLDAPUtils + */ + protected $utilClass; + + /** + * Get the utils class interface + * + * @return \adLDAP\classes\adLDAPUtils + */ + public function utilities() + { + if (!$this->utilClass) + { + $this->utilClass = new \adLDAP\classes\adLDAPUtils($this); + } + return $this->utilClass; + } + + /** + * The contacts class + * + * @var \adLDAP\classes\adLDAPContacts + */ + protected $contactClass; + + /** + * Get the contacts class interface + * + * @return \adLDAP\classes\adLDAPContacts + */ + public function contact() + { + if (!$this->contactClass) + { + $this->contactClass = new \adLDAP\classes\adLDAPContacts($this); + } + return $this->contactClass; + } + + /** + * The exchange class + * + * @var \adLDAP\classes\adLDAPExchange + */ + protected $exchangeClass; + + /** + * Get the exchange class interface + * + * @return \adLDAP\classes\adLDAPExchange + */ + public function exchange() + { + if (!$this->exchangeClass) + { + $this->exchangeClass = new \adLDAP\classes\adLDAPExchange($this); + } + return $this->exchangeClass; + } + + /** + * The computers class + * + * @var \adLDAP\classes\adLDAPComputers + */ + protected $computerClass; + + /** + * Get the computers class interface + * + * @return \adLDAP\classes\adLDAPComputers + */ + public function computer() + { + if (!$this->computerClass) + { + $this->computerClass = new \adLDAP\classes\adLDAPComputers($this); + } + return $this->computerClass; + } + + /** + * Getters and Setters + */ + + /** + * Set the account suffix + * + * @param string $accountSuffix + * @return void + */ + public function setAccountSuffix($accountSuffix) + { + $this->accountSuffix = $accountSuffix; + } + + /** + * Get the account suffix + * + * @return string + */ + public function getAccountSuffix() + { + return $this->accountSuffix; + } + + /** + * Set the domain controllers array + * + * @param array $domainControllers + * @return void + */ + public function setDomainControllers(array $domainControllers) + { + $this->domainControllers = $domainControllers; + } + + /** + * Get the list of domain controllers + * + * @return array + */ + public function getDomainControllers() + { + return $this->domainControllers; + } + + /** + * Sets the port number your domain controller communicates over + * + * @param int $adPort + */ + public function setPort($adPort) + { + $this->adPort = $adPort; + } + + /** + * Gets the port number your domain controller communicates over + * + * @return int + */ + public function getPort() + { + return $this->adPort; + } + + /** + * Set the username of an account with higher priviledges + * + * @param string $adminUsername + * @return void + */ + public function setAdminUsername($adminUsername) + { + $this->adminUsername = $adminUsername; + } + + /** + * Get the username of the account with higher priviledges + * + * This will throw an exception for security reasons + */ + public function getAdminUsername() + { + throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); + } + + /** + * Set the password of an account with higher priviledges + * + * @param string $adminPassword + * @return void + */ + public function setAdminPassword($adminPassword) + { + $this->adminPassword = $adminPassword; + } + + /** + * Get the password of the account with higher priviledges + * + * This will throw an exception for security reasons + */ + public function getAdminPassword() + { + throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); + } + + /** + * Set whether to detect the true primary group + * + * @param bool $realPrimaryGroup + * @return void + */ + public function setRealPrimaryGroup($realPrimaryGroup) + { + $this->realPrimaryGroup = $realPrimaryGroup; + } + + /** + * Get the real primary group setting + * + * @return bool + */ + public function getRealPrimaryGroup() + { + return $this->realPrimaryGroup; + } + + /** + * Set whether to use SSL + * + * @param bool $useSSL + * @return void + */ + public function setUseSSL($useSSL) + { + $this->useSSL = $useSSL; + // Set the default port correctly + if ($this->useSSL) + { + $this->setPort(self::ADLDAP_LDAPS_PORT); + } + else + { + $this->setPort(self::ADLDAP_LDAP_PORT); + } + } + + /** + * Get the SSL setting + * + * @return bool + */ + public function getUseSSL() + { + return $this->useSSL; + } + + /** + * Set whether to use TLS + * + * @param bool $useTLS + * @return void + */ + public function setUseTLS($useTLS) + { + $this->useTLS = $useTLS; + } + + /** + * Get the TLS setting + * + * @return bool + */ + public function getUseTLS() + { + return $this->useTLS; + } + + /** + * Set whether to use SSO + * Requires ldap_sasl_bind support. Be sure --with-ldap-sasl is used when configuring PHP otherwise this function will be undefined. + * + * @param bool $useSSO + * @throws adLDAPException + */ + public function setUseSSO($useSSO) + { + if ($useSSO === true && !$this->ldapSaslSupported()) + { + throw new adLDAPException('No LDAP SASL support for PHP. See: http://www.php.net/ldap_sasl_bind'); + } + $this->useSSO = $useSSO; + } + + /** + * Get the SSO setting + * + * @return bool + */ + public function getUseSSO() + { + return $this->useSSO; + } + + /** + * Set whether to lookup recursive groups + * + * @param bool $recursiveGroups + * @return void + */ + public function setRecursiveGroups($recursiveGroups) + { + $this->recursiveGroups = $recursiveGroups; + } + + /** + * Get the recursive groups setting + * + * @return bool + */ + public function getRecursiveGroups() + { + return $this->recursiveGroups; + } + + /** + * Default Constructor + * + * Tries to bind to the AD domain over LDAP or LDAPs + * + * @param array $options Array of options to pass to the constructor + * @throws adLDAPException - if unable to bind to Domain Controller + * @return bool + */ + function __construct($options = array()) + { + // You can specifically overide any of the default configuration options setup above + if (count($options) > 0) + { + if (array_key_exists("account_suffix", $options)) + { + $this->accountSuffix = $options["account_suffix"]; + } + if (array_key_exists("base_dn", $options)) + { + $this->baseDn = $options["base_dn"]; + } + if (array_key_exists("domain_controllers", $options)) + { + if (!is_array($options["domain_controllers"])) + { + throw new adLDAPException('[domain_controllers] option must be an array'); + } + $this->domainControllers = $options["domain_controllers"]; + } + if (array_key_exists("admin_username", $options)) + { + $this->adminUsername = $options["admin_username"]; + } + if (array_key_exists("admin_password", $options)) + { + $this->adminPassword = $options["admin_password"]; + } + if (array_key_exists("real_primarygroup", $options)) + { + $this->realPrimaryGroup = $options["real_primarygroup"]; + } + if (array_key_exists("use_ssl", $options)) + { + $this->setUseSSL($options["use_ssl"]); + } + if (array_key_exists("use_tls", $options)) + { + $this->useTLS = $options["use_tls"]; + } + if (array_key_exists("recursive_groups", $options)) + { + $this->recursiveGroups = $options["recursive_groups"]; + } + if (array_key_exists("follow_referrals", $options)) + { + $this->followReferrals = $options["follow_referrals"]; + } + if (array_key_exists("ad_port", $options)) + { + $this->setPort($options["ad_port"]); + } + if (array_key_exists("sso", $options)) + { + $this->setUseSSO($options["sso"]); + if (!$this->ldapSaslSupported()) + { + $this->setUseSSO(false); + } + } + } + + if ($this->ldapSupported() === false) + { + throw new adLDAPException('No LDAP support for PHP. See: http://www.php.net/ldap'); + } + + return $this->connect(); + } + + /** + * Default Destructor + * + * Closes the LDAP connection + * + * @return void + */ + function __destruct() + { + $this->close(); + } + + /** + * Connects and Binds to the Domain Controller + * + * @return bool + * @throws adLDAPException + */ + public function connect() + { + // Connect to the AD/LDAP server as the username/password + $domainController = $this->randomController(); + if ($this->useSSL) + { + $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort); + } + else + { + $this->ldapConnection = ldap_connect("ldap://" . $domainController, $this->adPort); + } + + // Set some ldap options for talking to AD + ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, $this->followReferrals); + + if ($this->useTLS) + { + ldap_start_tls($this->ldapConnection); + } + + // Bind as a domain admin if they've set it up + if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) + { + $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword); + if (!$this->ldapBind) + { + if ($this->useSSL && !$this->useTLS) + { + // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message + throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError()); + } + else + { + throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError()); + } + } + } + if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) + { + putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']); + $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); + if (!$this->ldapBind) + { + throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); + } + else + { + return true; + } + } + + if ($this->baseDn == NULL) + { + $this->baseDn = $this->findBaseDn(); + } + return true; + } + + /** + * Closes the LDAP connection + * + * @return void + */ + public function close() + { + if ($this->ldapConnection) + { + @ldap_close($this->ldapConnection); + } + } + + /** + * Validate a user's login credentials + * + * @param string $username A user's AD username + * @param string $password A user's AD password + * @param bool $preventRebind optional + * @return bool + * @throws adLDAPException + */ + public function authenticate($username, $password, $preventRebind = false) + { + /** @noinspection PhpUndefinedClassInspection */ + Logging::var_trace('username', $username); + /** @noinspection PhpUndefinedClassInspection */ + Logging::var_trace('preventRebind', $preventRebind); + + // Prevent null binding + if ($username === NULL || $password === NULL) + { + return false; + } + if (empty($username) || empty($password)) + { + return false; + } + + // Allow binding over SSO for Kerberos + if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) + { + /** @noinspection PhpUndefinedClassInspection */ + Logging::trace('putenv("KRB5CCNAME="' . $_SERVER['KRB5CCNAME'] . '" and perform ldap_sasl_bind'); + putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']); + $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI"); + if (!$this->ldapBind) + { + throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); + } + else + { + return true; + } + } + + /** @noinspection PhpUndefinedClassInspection */ + Logging::trace('ldap_bind(' . $this->ldapConnection . ', ' . $username . $this->accountSuffix . ', $password'); + + // Bind as the user + $ret = true; + $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password); + if (!$this->ldapBind) + { + $ret = false; + } + + // Once we've checked their details, kick back into admin mode if we have it + if ($this->adminUsername !== NULL && !$preventRebind) + { + /** @noinspection PhpUndefinedClassInspection */ + Logging::trace('ldap_bind(' . $this->ldapConnection . ', ' . $this->adminUsername . $this->accountSuffix . ', $this->adminPassword'); + + $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword); + if (!$this->ldapBind) + { + // This should never happen in theory + throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError()); + } + } + return $ret; + } + + /** + * Return a list of all found objects (except computer) in AD + * $search has to match either cn, displayname, samaccountname or sn + * + * @param bool $includeDescription Return a description,cn, displayname and distinguishedname of the user + * @param string $search Search parameter + * @param bool $sorted Sort the user accounts + * @return array|bool , if $includeDescription=true then a multi-dimensional array + * @throws Exception + */ + public function search($includeDescription = false, $search = "*", $sorted = true) + { + if (!$this->getLdapBind()) + { + return false; + } + + // Perform the search and grab all their details + //$filter = "(|(cn=" . $search . ")(displayname=" . $search . ")(samaccountname=" . $search . "))"; + $filter = "(&(!(objectClass=computer))(|(anr=" . $search . ")))"; + $fields = array("cn", "description", "displayname", "distinguishedname", "samaccountname"); + try + { + $sr = ldap_search($this->getLdapConnection(), $this->getBaseDn(), $filter, $fields); + } catch (Exception $e) + { + Logging::var_trace('filter', $filter); + Logging::var_trace('fields', $fields); + throw new Exception($e->getMessage() . "\nfilter = '" . $filter . "'\nfields = " . print_r($fields, true), 0, $e); + } + try + { + $entries = ldap_get_entries($this->getLdapConnection(), $sr); + } catch (Exception $e) + { + /** @noinspection PhpUndefinedVariableInspection */ + Logging::var_trace('sr', $sr); + throw new Exception($e->getMessage() . "\nsr = " . print_r($sr, true), 0, $e); + } + + $objectArray = array(); + for ($i = 0; $i < $entries["count"]; $i++) + { + if ($includeDescription && strlen($entries[$i]["description"][0]) > 0) + { + $objectArray[$entries[$i]["samaccountname"][0]] = array($entries[$i]["cn"][0], $entries[$i]["description"][0], $entries[$i]["displayname"][0], $entries[$i]["distinguishedname"][0]); + } + elseif ($includeDescription) + { + // description is set to displayname if no description is present + $objectArray[$entries[$i]["samaccountname"][0]] = array($entries[$i]["cn"][0], $entries[$i]["displayname"][0], $entries[$i]["displayname"][0], $entries[$i]["distinguishedname"][0]); + } + else + { + array_push($objectArray, $entries[$i]["samaccountname"][0]); + } + } + if ($sorted) + { + asort($objectArray); + } + return $objectArray; + } + + /** + * Returns objectClass in an array + * + * @param $distinguishedName + * @return array|bool + * @throws Exception + */ + public function getObjectClass($distinguishedName) + { + if ($distinguishedName === NULL) + { + return false; + } + if (!$this->getLdapBind()) + { + return false; + } + + $filter = "distinguishedName=" . $this->utilities()->ldapSlashes($distinguishedName); + + $fields = array("objectclass"); + try + { + $sr = ldap_search($this->getLdapConnection(), $this->getBaseDn(), $filter, $fields); + } catch (Exception $e) + { + Logging::var_trace('filter', $filter); + Logging::var_trace('fields', $fields); + throw new Exception($e->getMessage() . "\nfilter = '" . $filter . "'\nfields = " . print_r($fields, true), 0, $e); + } + + try + { + /** @noinspection PhpUndefinedVariableInspection */ + $entries = ldap_get_entries($this->getLdapConnection(), $sr); + } catch (Exception $e) + { + /** @noinspection PhpUndefinedVariableInspection */ + Logging::var_trace('sr', $sr); + throw new Exception($e->getMessage() . "\nsr = " . print_r($sr, true), 0, $e); + } + + $objects = array(); + for ($i = 0; $i < $entries[0]["objectclass"]["count"]; $i++) + { + array_push($objects, $entries[0]["objectclass"][$i]); + } + return $objects; + } + + /** + * Find the Base DN of your domain controller + * + * @return string + */ + public function findBaseDn() + { + $namingContext = $this->getRootDse(array('defaultnamingcontext')); + return $namingContext[0]['defaultnamingcontext'][0]; + } + + /** + * Get the RootDSE properties from a domain controller + * + * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext + * @return array|bool + */ + public function getRootDse($attributes = array("*", "+")) + { + if (!$this->ldapBind) + { + return (false); + } + + $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes); + $entries = @ldap_get_entries($this->ldapConnection, $sr); + return $entries; + } + + /** + * Get last error from Active Directory + * + * This function gets the last message from Active Directory + * This may indeed be a 'Success' message but if you get an unknown error + * it might be worth calling this function to see what errors were raised + * + * return string + */ + public function getLastError() + { + return @ldap_error($this->ldapConnection); + } + + /** + * Detect LDAP support in php + * + * @return bool + */ + protected function ldapSupported() + { + if (!function_exists('ldap_connect')) + { + return false; + } + return true; + } + + /** + * Detect ldap_sasl_bind support in PHP + * + * @return bool + */ + protected function ldapSaslSupported() + { + if (!function_exists('ldap_sasl_bind')) + { + return false; + } + return true; + } + + /** + * Schema + * + * @param array $attributes Attributes to be queried + * @return array|bool + */ + public function adldap_schema($attributes) + { + + // LDAP doesn't like NULL attributes, only set them if they have values + // If you wish to remove an attribute you should set it to a space + // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute + $mod = array(); + + // Check every attribute to see if it contains 8bit characters and then UTF8 encode them + array_walk($attributes, array($this, 'encode8bit')); + + if (isset($attributes["address_city"])) + { + $mod["l"][0] = $attributes["address_city"]; + } + if (isset($attributes["address_code"])) + { + $mod["postalCode"][0] = $attributes["address_code"]; + } + //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes? + if (isset($attributes["address_country"])) + { + $mod["c"][0] = $attributes["address_country"]; + } + if (isset($attributes["address_pobox"])) + { + $mod["postOfficeBox"][0] = $attributes["address_pobox"]; + } + if (isset($attributes["address_state"])) + { + $mod["st"][0] = $attributes["address_state"]; + } + if (isset($attributes["address_street"])) + { + $mod["streetAddress"][0] = $attributes["address_street"]; + } + if (isset($attributes["company"])) + { + $mod["company"][0] = $attributes["company"]; + } + if (isset($attributes["change_password"])) + { + $mod["pwdLastSet"][0] = 0; + } + if (isset($attributes["department"])) + { + $mod["department"][0] = $attributes["department"]; + } + if (isset($attributes["description"])) + { + $mod["description"][0] = $attributes["description"]; + } + if (isset($attributes["display_name"])) + { + $mod["displayName"][0] = $attributes["display_name"]; + } + if (isset($attributes["email"])) + { + $mod["mail"][0] = $attributes["email"]; + } + if (isset($attributes["expires"])) + { + $mod["accountExpires"][0] = $attributes["expires"]; + } //unix epoch format? + if (isset($attributes["firstname"])) + { + $mod["givenName"][0] = $attributes["firstname"]; + } + if (isset($attributes["home_directory"])) + { + $mod["homeDirectory"][0] = $attributes["home_directory"]; + } + if (isset($attributes["home_drive"])) + { + $mod["homeDrive"][0] = $attributes["home_drive"]; + } + if (isset($attributes["initials"])) + { + $mod["initials"][0] = $attributes["initials"]; + } + if (isset($attributes["logon_name"])) + { + $mod["userPrincipalName"][0] = $attributes["logon_name"]; + } + if (isset($attributes["manager"])) + { + $mod["manager"][0] = $attributes["manager"]; + } //UNTESTED ***Use DistinguishedName*** + if (isset($attributes["office"])) + { + $mod["physicalDeliveryOfficeName"][0] = $attributes["office"]; + } + if (isset($attributes["password"])) + { + $mod["unicodePwd"][0] = $this->user()->encodePassword($attributes["password"]); + } + if (isset($attributes["profile_path"])) + { + $mod["profilepath"][0] = $attributes["profile_path"]; + } + if (isset($attributes["script_path"])) + { + $mod["scriptPath"][0] = $attributes["script_path"]; + } + if (isset($attributes["surname"])) + { + $mod["sn"][0] = $attributes["surname"]; + } + if (isset($attributes["title"])) + { + $mod["title"][0] = $attributes["title"]; + } + if (isset($attributes["telephone"])) + { + $mod["telephoneNumber"][0] = $attributes["telephone"]; + } + if (isset($attributes["mobile"])) + { + $mod["mobile"][0] = $attributes["mobile"]; + } + if (isset($attributes["pager"])) + { + $mod["pager"][0] = $attributes["pager"]; + } + if (isset($attributes["ipphone"])) + { + $mod["ipphone"][0] = $attributes["ipphone"]; + } + if (isset($attributes["web_page"])) + { + $mod["wWWHomePage"][0] = $attributes["web_page"]; + } + if (isset($attributes["fax"])) + { + $mod["facsimileTelephoneNumber"][0] = $attributes["fax"]; + } + if (isset($attributes["enabled"])) + { + $mod["userAccountControl"][0] = $attributes["enabled"]; + } + if (isset($attributes["homephone"])) + { + $mod["homephone"][0] = $attributes["homephone"]; + } + + // Distribution List specific schema + if (isset($attributes["group_sendpermission"])) + { + $mod["dlMemSubmitPerms"][0] = $attributes["group_sendpermission"]; + } + if (isset($attributes["group_rejectpermission"])) + { + $mod["dlMemRejectPerms"][0] = $attributes["group_rejectpermission"]; + } + + // Exchange Schema + if (isset($attributes["exchange_homemdb"])) + { + $mod["homeMDB"][0] = $attributes["exchange_homemdb"]; + } + if (isset($attributes["exchange_mailnickname"])) + { + $mod["mailNickname"][0] = $attributes["exchange_mailnickname"]; + } + if (isset($attributes["exchange_proxyaddress"])) + { + $mod["proxyAddresses"][0] = $attributes["exchange_proxyaddress"]; + } + if (isset($attributes["exchange_usedefaults"])) + { + $mod["mDBUseDefaults"][0] = $attributes["exchange_usedefaults"]; + } + if (isset($attributes["exchange_policyexclude"])) + { + $mod["msExchPoliciesExcluded"][0] = $attributes["exchange_policyexclude"]; + } + if (isset($attributes["exchange_policyinclude"])) + { + $mod["msExchPoliciesIncluded"][0] = $attributes["exchange_policyinclude"]; + } + if (isset($attributes["exchange_addressbook"])) + { + $mod["showInAddressBook"][0] = $attributes["exchange_addressbook"]; + } + if (isset($attributes["exchange_altrecipient"])) + { + $mod["altRecipient"][0] = $attributes["exchange_altrecipient"]; + } + if (isset($attributes["exchange_deliverandredirect"])) + { + $mod["deliverAndRedirect"][0] = $attributes["exchange_deliverandredirect"]; + } + + // This schema is designed for contacts + if (isset($attributes["exchange_hidefromlists"])) + { + $mod["msExchHideFromAddressLists"][0] = $attributes["exchange_hidefromlists"]; + } + if (isset($attributes["contact_email"])) + { + $mod["targetAddress"][0] = $attributes["contact_email"]; + } + + //echo (" "); 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; + } + + /** + * Convert 8bit characters e.g. accented characters to UTF8 encoded characters + */ + protected function encode8Bit(&$item, $key) + { + $encode = false; + if (is_string($item)) + { + for ($i = 0; $i < strlen($item); $i++) + { + if (ord($item[$i]) >> 7) + { + $encode = true; + } + } + } + if ($encode === true && $key != 'password') + { + $item = utf8_encode($item); + } + } + + /** + * Select a random domain controller from your domain controller array + * + * @return string + */ + protected function randomController() + { + mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions + /*if (sizeof($this->domainControllers) > 1) { + $adController = $this->domainControllers[array_rand($this->domainControllers)]; + // Test if the controller is responding to pings + $ping = $this->pingController($adController); + if ($ping === false) { + // Find the current key in the domain controllers array + $key = array_search($adController, $this->domainControllers); + // Remove it so that we don't end up in a recursive loop + unset($this->domainControllers[$key]); + // Select a new controller + return $this->randomController(); + } + else { + return ($adController); + } + } */ + return $this->domainControllers[array_rand($this->domainControllers)]; + } + + /** + * Test basic connectivity to controller + * + * @param $host + * @return bool + */ + protected function pingController($host) + { + $port = $this->adPort; + fsockopen($host, $port, $errno, $errstr, 10); + if ($errno > 0) + { + return false; + } + return true; + } } /** -* adLDAP Exception Handler -* -* Exceptions of this type are thrown on bind failure or when SSL is required but not configured -* Example: -* try { -* $adldap = new adLDAP(); -* } -* catch (adLDAPException $e) { -* echo $e; -* exit(); -* } -*/ -class adLDAPException extends \Exception {} + * adLDAP Exception Handler + * + * Exceptions of this type are thrown on bind failure or when SSL is required but not configured + * Example: + * try { + * $adldap = new adLDAP(); + * } + * catch (adLDAPException $e) { + * echo $e; + * exit(); + * } + */ +class adLDAPException extends \Exception +{ +} ?> diff --git a/lib/adLDAP/classes/adLDAPUsers.php b/lib/adLDAP/classes/adLDAPUsers.php index c3ea8c6..a800f55 100644 --- a/lib/adLDAP/classes/adLDAPUsers.php +++ b/lib/adLDAP/classes/adLDAPUsers.php @@ -1,31 +1,36 @@ adldap = $adldap; - } - - /** - * Validate a user's login credentials - * - * @param string $username A user's AD username - * @param string $password A user's AD password - * @param bool optional $prevent_rebind - * @return bool - */ - public function authenticate($username, $password, $preventRebind = false) { - return $this->adldap->authenticate($username, $password, $preventRebind); - } - - /** - * Create a user - * - * If you specify a password here, this can only be performed over SSL - * - * @param array $attributes The attributes to set to the user account - * @return bool - */ - public function 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->adldap->getUseSSL() && !$this->adldap->getUseTLS())) { - throw new \adLDAP\adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); - } - - if (!array_key_exists("display_name", $attributes)) { - $attributes["display_name"] = $attributes["firstname"] . " " . $attributes["surname"]; - } - - // Translate the schema - $add = $this->adldap->adldap_schema($attributes); - - // Additional stuff only used for adding accounts - $add["cn"][0] = $attributes["display_name"]; - $add["samaccountname"][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->accountControl($control_options); - - // Determine the container - $attributes["container"] = array_reverse($attributes["container"]); - $container = "OU=" . implode(", OU=",$attributes["container"]); - - // Add the entry - $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); - if ($result != true) { - return false; - } - return true; - } - - /** - * Account control options - * - * @param array $options The options to convert to int - * @return int - */ - protected function accountControl($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; - } - - /** - * Delete a user account - * - * @param string $username The username to delete (please be careful here!) - * @param bool $isGUID Is the username a GUID or a samAccountName - * @return array - */ - public function delete($username, $isGUID = false) { - $userinfo = $this->info($username, array("*"), $isGUID); - $dn = $userinfo[0]['distinguishedname'][0]; - $result = $this->adldap->folder()->delete($dn); - if ($result != true) { - return false; - } - return true; - } - - /** - * Groups the user is a member of - * - * @param string $username The username to query - * @param bool $recursive Recursive list of groups - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return array - */ - public function groups($username, $recursive = NULL, $isGUID = false) { - if ($username === NULL) { return false; } - if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it - if (!$this->adldap->getLdapBind()) { return false; } - - // Search the directory for their information - $info = @$this->info($username, array("memberof", "primarygroupid"), $isGUID); - $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames) - - if ($recursive === true) { - foreach ($groups as $id => $groupName) { - $extraGroups = $this->adldap->group()->recursiveGroups($groupName); - $groups = array_merge($groups, $extraGroups); - } - } - return $groups; - } - - /** - * Find information about the users. Returned in a raw array format from AD - * - * @param string $username The username to query - * @param array $fields Array of parameters to query - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return array - */ - public function info($username, $fields = NULL, $isGUID = false) { - if ($username === NULL) { return false; } - if (!$this->adldap->getLdapBind()) { return false; } - - if ($isGUID === true) { - $username = $this->adldap->utilities()->strGuidToHex($username); - $filter = "objectguid=" . $username; - } - else if (strpos($username, "@")) { - $filter = "userPrincipalName=" . $username; - } - else { - $filter = "samaccountname=" . $username; - } - $filter = "(&(objectCategory=person)({$filter}))"; - if ($fields === NULL) { - $fields = array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); - } - if (!in_array("objectsid", $fields)) { - $fields[] = "objectsid"; - } - $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); - $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); - - if (isset($entries[0])) { - if ($entries[0]['count'] >= 1) { - if (in_array("memberof", $fields)) { - // AD does not return the primary group in the ldap query, we may need to fudge it - if ($this->adldap->getRealPrimaryGroup() && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])) { - //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); - $entries[0]["memberof"][] = $this->adldap->group()->getPrimaryGroup($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); - } else { - $entries[0]["memberof"][] = "CN=Domain Users,CN=Users," . $this->adldap->getBaseDn(); - } - if (!isset($entries[0]["memberof"]["count"])) { - $entries[0]["memberof"]["count"] = 0; - } - $entries[0]["memberof"]["count"]++; - } - } - return $entries; - } - return false; - } - - /** - * Find information about the users. Returned in a raw array format from AD - * - * @param string $username The username to query - * @param array $fields Array of parameters to query - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return mixed - */ - public function infoCollection($username, $fields = NULL, $isGUID = false) { - if ($username === NULL) { return false; } - if (!$this->adldap->getLdapBind()) { return false; } - - $info = $this->info($username, $fields, $isGUID); - - if ($info !== false) { - $collection = new \adLDAP\collections\adLDAPUserCollection($info, $this->adldap); - return $collection; - } - return false; - } - - /** - * Determine if a user is in a specific group - * - * @param string $username The username to query - * @param string $group The name of the group to check against - * @param bool $recursive Check groups recursively - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return bool - */ - public function inGroup($username, $group, $recursive = NULL, $isGUID = false) { - if ($username === NULL) { return false; } - if ($group === NULL) { return false; } - if (!$this->adldap->getLdapBind()) { return false; } - if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it - - // Get a list of the groups - $groups = $this->groups($username, $recursive, $isGUID); - - // Return true if the specified group is in the group list - if (in_array($group, $groups)) { - return true; - } - return false; - } - - /** - * Determine a user's password expiry date - * - * @param string $username The username to query - * @param book $isGUID Is the username passed a GUID or a samAccountName - * @requires bcmath http://www.php.net/manual/en/book.bc.php - * @return array - */ - public function passwordExpiry($username, $isGUID = false) { - if ($username === NULL) { return "Missing compulsory field [username]"; } - if (!$this->adldap->getLdapBind()) { return false; } - if (!function_exists('bcmod')) { throw new \adLDAP\adLDAPException("Missing function support [bcmod] http://www.php.net/manual/en/book.bc.php"); }; - - $userInfo = $this->info($username, array("pwdlastset", "useraccountcontrol"), $isGUID); - $pwdLastSet = $userInfo[0]['pwdlastset'][0]; - $status = array(); - - if ($userInfo[0]['useraccountcontrol'][0] == '66048') { - // Password does not expire - return "Does not expire"; - } - if ($pwdLastSet === '0') { - // Password has already expired - return "Password has expired"; - } - - // Password expiry in AD can be calculated from TWO values: - // - User's own pwdLastSet attribute: stores the last time the password was changed - // - Domain's maxPwdAge attribute: how long passwords last in the domain - // - // Although Microsoft chose to use a different base and unit for time measurements. - // This function will convert them to Unix timestamps - $sr = ldap_read($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), 'objectclass=*', array('maxPwdAge')); - if (!$sr) { - return false; - } - $info = ldap_get_entries($this->adldap->getLdapConnection(), $sr); - $maxPwdAge = $info[0]['maxpwdage'][0]; - - // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx - // - // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC), - // stored in a 64 bit integer. - // - // The number of seconds between this date and Unix epoch is 11644473600. - // - // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond - // intervals from the time the password was set before the password expires. - // - // We also need to scale this to seconds but also this value is a _negative_ quantity! - // - // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire - // - // Unfortunately the maths involved are too big for PHP integers, so I've had to require - // BCMath functions to work with arbitrary precision numbers. - if (bcmod($maxPwdAge, 4294967296) === '0') { - return "Domain does not expire passwords"; - } - - // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's - // time units. Because maxpwd age is negative we need to subtract it. - $pwdExpire = bcsub($pwdLastSet, $maxPwdAge); - - // Convert MS's time to Unix time - $status['expiryts'] = bcsub(bcdiv($pwdExpire, '10000000'), '11644473600'); - $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdExpire, '10000000'), '11644473600')); - - return $status; - } - - /** - * Modify a user - * - * @param string $username The username to query - * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return bool - */ - public function modify($username, $attributes, $isGUID = false) { - if ($username === NULL) { return "Missing compulsory field [username]"; } - if (array_key_exists("password", $attributes) && !$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) { - throw new \adLDAP\adLDAPException('SSL/TLS must be configured on your webserver and enabled in the class to set passwords.'); - } - - // Find the dn of the user - $userDn = $this->dn($username, $isGUID); - if ($userDn === false) { - return false; - } - - // Translate the update to the LDAP schema - $mod = $this->adldap->adldap_schema($attributes); - - // Check to see if this is an enabled status update - if (!$mod && !array_key_exists("enabled", $attributes)) { - return false; - } - - // Set the account control attribute (only if specified) - if (array_key_exists("enabled", $attributes)) { - if ($attributes["enabled"]) { - $controlOptions = array("NORMAL_ACCOUNT"); - } - else { - $controlOptions = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); - } - $mod["userAccountControl"][0] = $this->accountControl($controlOptions); - } - - // Do the update - $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); - if ($result == false) { - return false; - } - return true; - } - - /** - * Disable a user account - * - * @param string $username The username to disable - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return bool - */ - public function disable($username, $isGUID = false) { - if ($username === NULL) { return "Missing compulsory field [username]"; } - $attributes = array("enabled" => 0); - $result = $this->modify($username, $attributes, $isGUID); - if ($result == false) { return false; } - - return true; - } - - /** - * Enable a user account - * - * @param string $username The username to enable - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return bool - */ - public function enable($username, $isGUID = false) { - if ($username === NULL) { return "Missing compulsory field [username]"; } - $attributes = array("enabled" => 1); - $result = $this->modify($username, $attributes, $isGUID); - if ($result == false) { return false; } - - return true; - } - - /** - * Set the password of a user - This must be performed over SSL - * - * @param string $username The username to modify - * @param string $password The new password - * @param bool $isGUID Is the username passed a GUID or a samAccountName - * @return bool - */ - public function password($username, $password, $isGUID = false) { - if ($username === NULL) { return false; } - if ($password === NULL) { return false; } - if (!$this->adldap->getLdapBind()) { return false; } - if (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) { - throw new \adLDAP\adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); - } - - $userDn = $this->dn($username, $isGUID); - if ($userDn === false) { - return false; - } - - $add=array(); - $add["unicodePwd"][0] = $this->encodePassword($password); - - $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add); - if ($result === false) { - $err = ldap_errno($this->adldap->getLdapConnection()); - if ($err) { - $msg = 'Error ' . $err . ': ' . ldap_err2str($err) . '.'; - if($err == 53) { - $msg .= ' Your password might not match the password policy.'; - } - throw new \adLDAP\adLDAPException($msg); - } - else { - return false; - } - } - return true; - } - - /** - * Encode a password for transmission over LDAP - * - * @param string $password The password to encode - * @return string - */ - public function encodePassword($password) { - $password="\"".$password."\""; - $encoded=""; - for ($i=0; $iinfo($username, array("cn"), $isGUID); - if ($user[0]["dn"] === NULL) { - return false; - } - $userDn = $user[0]["dn"]; - return $userDn; - } - - /** - * Return a list of all users in AD - * - * @param bool $includeDescription Return a description of the user - * @param string $search Search parameter - * @param bool $sorted Sort the user accounts - * @return array - */ - public function all($includeDescription = false, $search = "*", $sorted = true) { - if (!$this->adldap->getLdapBind()) { return false; } - - // Perform the search and grab all their details - $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=" . $search . "))"; - $fields = array("samaccountname","displayname"); - $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); - $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); - - $usersArray = array(); - for ($i=0; $i<$entries["count"]; $i++) { - if ($includeDescription && strlen($entries[$i]["displayname"][0])>0) { - $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; - } elseif ($includeDescription) { - $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; - } else { - array_push($usersArray, $entries[$i]["samaccountname"][0]); - } - } - if ($sorted) { - asort($usersArray); - } - return $usersArray; - } - - /** - * Converts a username (samAccountName) to a GUID - * - * @param string $username The username to query - * @return string - */ - public function usernameToGuid($username) { - if (!$this->adldap->getLdapBind()){ return false; } - if ($username === null){ return "Missing compulsory field [username]"; } - - $filter = "samaccountname=" . $username; - $fields = array("objectGUID"); - $sr = @ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); - if (ldap_count_entries($this->adldap->getLdapConnection(), $sr) > 0) { - $entry = @ldap_first_entry($this->adldap->getLdapConnection(), $sr); - $guid = @ldap_get_values_len($this->adldap->getLdapConnection(), $entry, 'objectGUID'); - $strGUID = $this->adldap->utilities()->binaryToText($guid[0]); - return $strGUID; - } - return false; - } - - /** - * Return a list of all users in AD that have a specific value in a field - * - * @param bool $includeDescription Return a description of the user - * @param string $searchField Field to search search for - * @param string $searchFilter Value to search for in the specified field - * @param bool $sorted Sort the user accounts - * @return array - */ - public function find($includeDescription = false, $searchField = false, $searchFilter = false, $sorted = true) { - if (!$this->adldap->getLdapBind()) { return false; } - - // Perform the search and grab all their details - $searchParams = ""; - if ($searchField) { - $searchParams = "(" . $searchField . "=" . $searchFilter . ")"; - } - $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)" . $searchParams . ")"; - $fields = array("samaccountname","displayname"); - $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); - $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); - - $usersArray = array(); - for ($i=0; $i < $entries["count"]; $i++) { - if ($includeDescription && strlen($entries[$i]["displayname"][0]) > 0) { - $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; - } - else if ($includeDescription) { - $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; - } - else { - array_push($usersArray, $entries[$i]["samaccountname"][0]); - } - } - if ($sorted) { - asort($usersArray); - } - return ($usersArray); - } - - /** - * Move a user account to a different OU - * - * @param string $username The username to move (please be careful here!) - * @param array $container The container or containers to move the user to (please be careful here!). - * accepts containers in 1. parent 2. child order - * @return array - */ - public function move($username, $container) { - if (!$this->adldap->getLdapBind()) { return false; } - if ($username === null) { return "Missing compulsory field [username]"; } - if ($container === null) { return "Missing compulsory field [container]"; } - if (!is_array($container)) { return "Container must be an array"; } - - $userInfo = $this->info($username, array("*")); - $dn = $userInfo[0]['distinguishedname'][0]; - $newRDn = "cn=" . $username; - $container = array_reverse($container); - $newContainer = "ou=" . implode(",ou=",$container); - $newBaseDn = strtolower($newContainer) . "," . $this->adldap->getBaseDn(); - $result = @ldap_rename($this->adldap->getLdapConnection(), $dn, $newRDn, $newBaseDn, true); - if ($result !== true) { - return false; - } - return true; - } - - /** - * Get the last logon time of any user as a Unix timestamp - * - * @param string $username - * @return long $unixTimestamp - */ - public function getLastLogon($username) { - if (!$this->adldap->getLdapBind()) { return false; } - if ($username === null) { return "Missing compulsory field [username]"; } - $userInfo = $this->info($username, array("lastLogonTimestamp")); - $lastLogon = adLDAPUtils::convertWindowsTimeToUnixTime($userInfo[0]['lastLogonTimestamp'][0]); - return $lastLogon; - } + * USER FUNCTIONS + */ +class adLDAPUsers +{ + /** + * The current adLDAP connection via dependency injection + * + * @var adLDAP + */ + protected $adldap; + + public function __construct(adLDAP $adldap) + { + $this->adldap = $adldap; + } + + /** + * Validate a user's login credentials + * + * @param string $username A user's AD username + * @param string $password A user's AD password + * @param bool $prevent_rebind optional + * @return bool + */ + public function authenticate($username, $password, $preventRebind = false) + { + return $this->adldap->authenticate($username, $password, $preventRebind); + } + + /** + * Create a user + * + * If you specify a password here, this can only be performed over SSL + * + * @param array $attributes The attributes to set to the user account + * @return bool + */ + public function 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->adldap->getUseSSL() && !$this->adldap->getUseTLS())) + { + throw new \adLDAP\adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); + } + + if (!array_key_exists("display_name", $attributes)) + { + $attributes["display_name"] = $attributes["firstname"] . " " . $attributes["surname"]; + } + + // Translate the schema + $add = $this->adldap->adldap_schema($attributes); + + // Additional stuff only used for adding accounts + $add["cn"][0] = $attributes["display_name"]; + $add["samaccountname"][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->accountControl($control_options); + + // Determine the container + $attributes["container"] = array_reverse($attributes["container"]); + $container = "OU=" . implode(", OU=", $attributes["container"]); + + // Add the entry + $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); + if ($result != true) + { + return false; + } + return true; + } + + /** + * Account control options + * + * @param array $options The options to convert to int + * @return int + */ + protected function accountControl($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; + } + + /** + * Delete a user account + * + * @param string $username The username to delete (please be careful here!) + * @param bool $isGUID Is the username a GUID or a samAccountName + * @return bool + */ + public function delete($username, $isGUID = false) + { + $userinfo = $this->info($username, array("*"), $isGUID); + $dn = $userinfo[0]['distinguishedname'][0]; + $result = $this->adldap->folder()->delete($dn); + if ($result != true) + { + return false; + } + return true; + } + + /** + * Groups the user is a member of + * + * @param string $username The username to query + * @param bool $recursive Recursive list of groups + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return array|bool + */ + public function groups($username, $recursive = NULL, $isGUID = false) + { + if ($username === NULL) + { + return false; + } + if ($recursive === NULL) + { + $recursive = $this->adldap->getRecursiveGroups(); + } // Use the default option if they haven't set it + if (!$this->adldap->getLdapBind()) + { + return false; + } + + // Search the directory for their information + $info = @$this->info($username, array("memberof", "primarygroupid"), $isGUID); + $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames) + + if ($recursive === true) + { + foreach ($groups as $id => $groupName) + { + $extraGroups = $this->adldap->group()->recursiveGroups($groupName); + $groups = array_merge($groups, $extraGroups); + } + } + return $groups; + } + + /** + * Find information about the users. Returned in a raw array format from AD + * + * @param string $username The username to query + * @param array $fields Array of parameters to query + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return array|bool + * @throws Exception + */ + public function info($username, $fields = NULL, $isGUID = false) + { + if ($username === NULL) + { + return false; + } + if (!$this->adldap->getLdapBind()) + { + return false; + } + + if ($isGUID === true) + { + $username = $this->adldap->utilities()->strGuidToHex($username); + $filter = "objectguid=" . $username; + } + else if (strpos($username, "@")) + { + $filter = "userPrincipalName=" . $username; + } + else + { + $filter = "|(samaccountname=" . $username . ')'; + $filter .= "(employeeID=" . $username . ')'; + } + $filter = "(&(objectCategory=person)({$filter}))"; + if ($fields === NULL) + { + $fields = array("samaccountname", "mail", "memberof", "department", "displayname", "telephonenumber", "primarygroupid", "objectsid"); + } + if (!in_array("objectsid", $fields)) + { + $fields[] = "objectsid"; + } + + Logging::var_trace(compact('filter', 'fields')); + + try + { + $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); + } catch (Exception $e) + { + Logging::var_trace('filter', $filter); + Logging::var_trace('fields', $fields); + throw new Exception($e->getMessage() . "\nfilter = '" . $filter . "'\nfields = " . print_r($fields, true), 0, $e); + } + + try + { + /** @noinspection PhpUndefinedVariableInspection */ + $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); + } catch (Exception $e) + { + /** @noinspection PhpUndefinedVariableInspection */ + Logging::var_trace('sr', $sr); + throw new Exception($e->getMessage() . "\nsr = " . print_r($sr, true), 0, $e); + } + + Logging::var_trace(compact('entries')); + + if (isset($entries[0])) + { + if ($entries[0]['count'] >= 1) + { + if (in_array("memberof", $fields)) + { + // AD does not return the primary group in the ldap query, we may need to fudge it + if ($this->adldap->getRealPrimaryGroup() && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])) + { + //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); + $entries[0]["memberof"][] = $this->adldap->group()->getPrimaryGroup($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); + } + else + { + $entries[0]["memberof"][] = "CN=Domain Users,CN=Users," . $this->adldap->getBaseDn(); + } + if (!isset($entries[0]["memberof"]["count"])) + { + $entries[0]["memberof"]["count"] = 0; + } + $entries[0]["memberof"]["count"]++; + } + } + return $entries; + } + return false; + } + + /** + * Find information about the users. Returned in a raw array format from AD + * + * @param string $username The username to query + * @param array $fields Array of parameters to query + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return mixed + */ + public function infoCollection($username, $fields = NULL, $isGUID = false) + { + if ($username === NULL) + { + return false; + } + if (!$this->adldap->getLdapBind()) + { + return false; + } + + $info = $this->info($username, $fields, $isGUID); + + if ($info !== false) + { + $collection = new \adLDAP\collections\adLDAPUserCollection($info, $this->adldap); + return $collection; + } + return false; + } + + /** + * Determine if a user is in a specific group + * + * @param string $username The username to query + * @param string $group The name of the group to check against + * @param bool $recursive Check groups recursively + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function inGroup($username, $group, $recursive = NULL, $isGUID = false) + { + if ($username === NULL) + { + return false; + } + if ($group === NULL) + { + return false; + } + if (!$this->adldap->getLdapBind()) + { + return false; + } + if ($recursive === NULL) + { + $recursive = $this->adldap->getRecursiveGroups(); + } // Use the default option if they haven't set it + + // Get a list of the groups + $groups = $this->groups($username, $recursive, $isGUID); + + // Return true if the specified group is in the group list + if (in_array($group, $groups)) + { + return true; + } + return false; + } + + /** + * Determine a user's password expiry date + * + * @param string $username The username to query + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return array|bool + * @throws \adLDAP\adLDAPException + * @requires bcmath http://www.php.net/manual/en/book.bc.php + */ + public function passwordExpiry($username, $isGUID = false) + { + if ($username === NULL) + { + return "Missing compulsory field [username]"; + } + if (!$this->adldap->getLdapBind()) + { + return false; + } + if (!function_exists('bcmod')) + { + throw new \adLDAP\adLDAPException("Missing function support [bcmod] http://www.php.net/manual/en/book.bc.php"); + }; + + $userInfo = $this->info($username, array("pwdlastset", "useraccountcontrol"), $isGUID); + $pwdLastSet = $userInfo[0]['pwdlastset'][0]; + $status = array(); + + if ($userInfo[0]['useraccountcontrol'][0] == '66048') + { + // Password does not expire + return "Does not expire"; + } + if ($pwdLastSet === '0') + { + // Password has already expired + return "Password has expired"; + } + + // Password expiry in AD can be calculated from TWO values: + // - User's own pwdLastSet attribute: stores the last time the password was changed + // - Domain's maxPwdAge attribute: how long passwords last in the domain + // + // Although Microsoft chose to use a different base and unit for time measurements. + // This function will convert them to Unix timestamps + $sr = ldap_read($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), 'objectclass=*', array('maxPwdAge')); + if (!$sr) + { + return false; + } + $info = ldap_get_entries($this->adldap->getLdapConnection(), $sr); + $maxPwdAge = $info[0]['maxpwdage'][0]; + + // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx + // + // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC), + // stored in a 64 bit integer. + // + // The number of seconds between this date and Unix epoch is 11644473600. + // + // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond + // intervals from the time the password was set before the password expires. + // + // We also need to scale this to seconds but also this value is a _negative_ quantity! + // + // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire + // + // Unfortunately the maths involved are too big for PHP integers, so I've had to require + // BCMath functions to work with arbitrary precision numbers. + if (bcmod($maxPwdAge, 4294967296) === '0') + { + return "Domain does not expire passwords"; + } + + // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's + // time units. Because maxpwd age is negative we need to subtract it. + $pwdExpire = bcsub($pwdLastSet, $maxPwdAge); + + // Convert MS's time to Unix time + $status['expiryts'] = bcsub(bcdiv($pwdExpire, '10000000'), '11644473600'); + $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdExpire, '10000000'), '11644473600')); + + return $status; + } + + /** + * Modify a user + * + * @param string $username The username to query + * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + * @throws \adLDAP\adLDAPException + */ + public function modify($username, $attributes, $isGUID = false) + { + if ($username === NULL) + { + return "Missing compulsory field [username]"; + } + if (array_key_exists("password", $attributes) && !$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) + { + throw new \adLDAP\adLDAPException('SSL/TLS must be configured on your webserver and enabled in the class to set passwords.'); + } + + // Find the dn of the user + $userDn = $this->dn($username, $isGUID); + if ($userDn === false) + { + return false; + } + + // Translate the update to the LDAP schema + $mod = $this->adldap->adldap_schema($attributes); + + // Check to see if this is an enabled status update + if (!$mod && !array_key_exists("enabled", $attributes)) + { + return false; + } + + // Set the account control attribute (only if specified) + if (array_key_exists("enabled", $attributes)) + { + if ($attributes["enabled"]) + { + $controlOptions = array("NORMAL_ACCOUNT"); + } + else + { + $controlOptions = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); + } + $mod["userAccountControl"][0] = $this->accountControl($controlOptions); + } + + // Do the update + $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); + if ($result == false) + { + return false; + } + return true; + } + + /** + * Disable a user account + * + * @param string $username The username to disable + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function disable($username, $isGUID = false) + { + if ($username === NULL) + { + return "Missing compulsory field [username]"; + } + $attributes = array("enabled" => 0); + $result = $this->modify($username, $attributes, $isGUID); + if ($result == false) + { + return false; + } + + return true; + } + + /** + * Enable a user account + * + * @param string $username The username to enable + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function enable($username, $isGUID = false) + { + if ($username === NULL) + { + return "Missing compulsory field [username]"; + } + $attributes = array("enabled" => 1); + $result = $this->modify($username, $attributes, $isGUID); + if ($result == false) + { + return false; + } + + return true; + } + + /** + * Set the password of a user - This must be performed over SSL + * + * @param string $username The username to modify + * @param string $password The new password + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + * @throws \adLDAP\adLDAPException + */ + public function password($username, $password, $isGUID = false) + { + if ($username === NULL) + { + return false; + } + if ($password === NULL) + { + return false; + } + if (!$this->adldap->getLdapBind()) + { + return false; + } + if (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) + { + throw new \adLDAP\adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); + } + + $userDn = $this->dn($username, $isGUID); + if ($userDn === false) + { + return false; + } + + $add = array(); + $add["unicodePwd"][0] = $this->encodePassword($password); + + $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add); + if ($result === false) + { + $err = ldap_errno($this->adldap->getLdapConnection()); + if ($err) + { + $msg = 'Error ' . $err . ': ' . ldap_err2str($err) . '.'; + if ($err == 53) + { + $msg .= ' Your password might not match the password policy.'; + } + throw new \adLDAP\adLDAPException($msg); + } + else + { + return false; + } + } + return true; + } + + /** + * Encode a password for transmission over LDAP + * + * @param string $password The password to encode + * @return string + */ + public function encodePassword($password) + { + $password = "\"" . $password . "\""; + $encoded = ""; + for ($i = 0; $i < strlen($password); $i++) + { + $encoded .= "{$password{$i}}\000"; + } + return $encoded; + } + + /** + * Obtain the user's distinguished name based on their userid + * + * + * @param string $username The username + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return string + */ + public function dn($username, $isGUID = false) + { + $user = $this->info($username, array("cn"), $isGUID); + if ($user[0]["dn"] === NULL) + { + return false; + } + $userDn = $user[0]["dn"]; + return $userDn; + } + + /** + * Return a list of all users in AD + * + * @param bool $includeDescription Return a description of the user + * @param string $search Search parameter + * @param bool $sorted Sort the user accounts + * @return array|bool + */ + public function all($includeDescription = false, $search = "*", $sorted = true) + { + if (!$this->adldap->getLdapBind()) + { + return false; + } + + // Perform the search and grab all their details + $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT . ")(objectCategory=person)(cn=" . $search . "))"; + $fields = array("samaccountname", "displayname"); + $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); + $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); + + $usersArray = array(); + for ($i = 0; $i < $entries["count"]; $i++) + { + if ($includeDescription && strlen($entries[$i]["displayname"][0]) > 0) + { + $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; + } + elseif ($includeDescription) + { + $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; + } + else + { + array_push($usersArray, $entries[$i]["samaccountname"][0]); + } + } + if ($sorted) + { + asort($usersArray); + } + return $usersArray; + } + + /** + * Converts a username (samAccountName) to a GUID + * + * @param string $username The username to query + * @return string + */ + public function usernameToGuid($username) + { + if (!$this->adldap->getLdapBind()) + { + return false; + } + if ($username === null) + { + return "Missing compulsory field [username]"; + } + + $filter = "samaccountname=" . $username; + $fields = array("objectGUID"); + $sr = @ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); + if (ldap_count_entries($this->adldap->getLdapConnection(), $sr) > 0) + { + $entry = @ldap_first_entry($this->adldap->getLdapConnection(), $sr); + $guid = @ldap_get_values_len($this->adldap->getLdapConnection(), $entry, 'objectGUID'); + $strGUID = $this->adldap->utilities()->binaryToText($guid[0]); + return $strGUID; + } + return false; + } + + /** + * Return a list of all users in AD that have a specific value in a field + * + * @param bool $includeDescription Return a description of the user + * @param string $searchField Field to search search for + * @param string $searchFilter Value to search for in the specified field + * @param bool $sorted Sort the user accounts + * @return array|bool + */ + public function find($includeDescription = false, $searchField = false, $searchFilter = false, $sorted = true) + { + if (!$this->adldap->getLdapBind()) + { + return false; + } + + // Perform the search and grab all their details + $searchParams = ""; + if ($searchField) + { + $searchParams = "(" . $searchField . "=" . $searchFilter . ")"; + } + $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT . ")(objectCategory=person)" . $searchParams . ")"; + $fields = array("samaccountname", "displayname"); + $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); + $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); + + $usersArray = array(); + for ($i = 0; $i < $entries["count"]; $i++) + { + if ($includeDescription && strlen($entries[$i]["displayname"][0]) > 0) + { + $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; + } + else if ($includeDescription) + { + $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; + } + else + { + array_push($usersArray, $entries[$i]["samaccountname"][0]); + } + } + if ($sorted) + { + asort($usersArray); + } + return ($usersArray); + } + + /** + * Move a user account to a different OU + * + * @param string $username The username to move (please be careful here!) + * @param array $container The container or containers to move the user to (please be careful here!). + * accepts containers in 1. parent 2. child order + * @return array|bool + */ + public function move($username, $container) + { + if (!$this->adldap->getLdapBind()) + { + return false; + } + if ($username === null) + { + return "Missing compulsory field [username]"; + } + if ($container === null) + { + return "Missing compulsory field [container]"; + } + if (!is_array($container)) + { + return "Container must be an array"; + } + + $userInfo = $this->info($username, array("*")); + $dn = $userInfo[0]['distinguishedname'][0]; + $newRDn = "cn=" . $username; + $container = array_reverse($container); + $newContainer = "ou=" . implode(",ou=", $container); + $newBaseDn = strtolower($newContainer) . "," . $this->adldap->getBaseDn(); + $result = @ldap_rename($this->adldap->getLdapConnection(), $dn, $newRDn, $newBaseDn, true); + if ($result !== true) + { + return false; + } + return true; + } + + /** + * Get the last logon time of any user as a Unix timestamp + * + * @param string $username + * @return float|bool $unixTimestamp + */ + public function getLastLogon($username) + { + if (!$this->adldap->getLdapBind()) + { + return false; + } + if ($username === null) + { + return "Missing compulsory field [username]"; + } + $userInfo = $this->info($username, array("lastLogonTimestamp")); + $lastLogon = adLDAPUtils::convertWindowsTimeToUnixTime($userInfo[0]['lastLogonTimestamp'][0]); + return $lastLogon; + } } + ?> diff --git a/lib/adLDAP/classes/adLDAPUtils.php b/lib/adLDAP/classes/adLDAPUtils.php index a039e7d..77d178f 100644 --- a/lib/adLDAP/classes/adLDAPUtils.php +++ b/lib/adLDAP/classes/adLDAPUtils.php @@ -252,8 +252,8 @@ public function getVersion() { /** * Round a Windows timestamp down to seconds and remove the seconds between 1601-01-01 and 1970-01-01 * - * @param long $windowsTime - * @return long $unixTime + * @param float $windowsTime + * @return float $unixTime */ public static function convertWindowsTimeToUnixTime($windowsTime) { $unixTime = round($windowsTime / 10000000) - 11644473600;