From cc63226762c39c22340b830a4daea6d4b3a55e21 Mon Sep 17 00:00:00 2001
From: Luke Shumaker
+Test Settings
+Use this form as a limited test for those settings you saved.* This will test user creation and group membership. If settings don't work, use another browser to try actually logging in. (unless you'd rather get locked out)
+*You did save them, right?
+
+Test Results:
+
+
+
";
+echo "simpleldap_directory_type: ".get_option("simpleldap_directory_type")."/".$_POST['LDAP']."
";
+echo "simpleldap_login_mode: ".get_option("simpleldap_login_mode")."/".$_POST['mode']."
";
+echo "simpleldap_group: ".get_option("simpleldap_group")."/".$_POST['group_name']."
";
+echo "simpleldap_account_type: ".get_option("simpleldap_account_type")."/".$_POST['create_type']."
"); print_r($add); + + // Determine the container + $attributes["container"]=array_reverse($attributes["container"]); + $container="OU=".implode(",OU=",$attributes["container"]); + + // Add the entry + $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add); + if ($result!=true){ return (false); } + + return (true); + } + + /** + * 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 user_delete($username,$isGUID=false) { + $userinfo = $this->user_info($username, array("*"),$isGUID); + $dn = $userinfo[0]['distinguishedname'][0]; + $result=$this->dn_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 user_groups($username,$recursive=NULL,$isGUID=false){ + if ($username===NULL){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it + if (!$this->_bind){ return (false); } + + // Search the directory for their information + $info=@$this->user_info($username,array("memberof","primarygroupid"),$isGUID); + $groups=$this->nice_names($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames) + + if ($recursive === true){ + foreach ($groups as $id => $group_name){ + $extra_groups=$this->recursive_groups($group_name); + $groups=array_merge($groups,$extra_groups); + } + } + + return ($groups); + } + + /** + * Find information about the users + * + * @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 user_info($username,$fields=NULL,$isGUID=false){ + if ($username===NULL){ return (false); } + if (!$this->_bind){ return (false); } + + if ($isGUID === true) { + $username = $this->strguid2hex($username); + $filter="objectguid=".$username; + } + else if (strstr($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->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $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->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])){ + //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); + $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); + } else { + $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn; + } + $entries[0]["memberof"]["count"]++; + } + } + return $entries; + } + 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 user_ingroup($username,$group,$recursive=NULL,$isGUID=false){ + if ($username===NULL){ return (false); } + if ($group===NULL){ return (false); } + if (!$this->_bind){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it + + // Get a list of the groups + $groups=$this->user_groups($username,$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 user_password_expiry($username,$isGUID=false) { + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if (!$this->_bind){ return (false); } + if (!function_exists('bcmod')) { return ("Missing function support [bcmod] http://www.php.net/manual/en/book.bc.php"); }; + + $userinfo = $this->user_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->_conn, $this->_base_dn, 'objectclass=*', array('maxPwdAge')); + if (!$sr) { + return false; + } + $info = ldap_get_entries($this->_conn, $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 user_modify($username,$attributes,$isGUID=false){ + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if (array_key_exists("password",$attributes) && !$this->_use_ssl){ + throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); + } + + // Find the dn of the user + $user_dn=$this->user_dn($username,$isGUID); + if ($user_dn===false){ return (false); } + + // Translate the update to the LDAP schema + $mod=$this->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"]){ $control_options=array("NORMAL_ACCOUNT"); } + else { $control_options=array("NORMAL_ACCOUNT","ACCOUNTDISABLE"); } + $mod["userAccountControl"][0]=$this->account_control($control_options); + } + + // Do the update + $result=@ldap_modify($this->_conn,$user_dn,$mod); + if ($result==false){ return (false); } + + return (true); + } + + /** + * 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 user_disable($username,$isGUID=false){ + if ($username===NULL){ return ("Missing compulsory field [username]"); } + $attributes=array("enabled"=>0); + $result = $this->user_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 user_enable($username,$isGUID=false){ + if ($username===NULL){ return ("Missing compulsory field [username]"); } + $attributes=array("enabled"=>1); + $result = $this->user_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 user_password($username,$password,$isGUID=false){ + if ($username===NULL){ return (false); } + if ($password===NULL){ return (false); } + if (!$this->_bind){ return (false); } + if (!$this->_use_ssl && !$this->_use_tls){ + throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); + } + + $user_dn=$this->user_dn($username,$isGUID); + if ($user_dn===false){ return (false); } + + $add=array(); + $add["unicodePwd"][0]=$this->encode_password($password); + + $result=ldap_mod_replace($this->_conn,$user_dn,$add); + if ($result==false){ return (false); } + + return (true); + } + + /** + * Return a list of all users in AD + * + * @param bool $include_desc Return a description of the user + * @param string $search Search parameter + * @param bool $sorted Sort the user accounts + * @return array + */ + public function all_users($include_desc = false, $search = "*", $sorted = true){ + if (!$this->_bind){ return (false); } + + // Perform the search and grab all their details + $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=".$search."))"; + $fields=array("samaccountname","displayname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + $users_array = array(); + for ($i=0; $i<$entries["count"]; $i++){ + if ($include_desc && strlen($entries[$i]["displayname"][0])>0){ + $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["displayname"][0]; + } elseif ($include_desc){ + $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0]; + } else { + array_push($users_array, $entries[$i]["samaccountname"][0]); + } + } + if ($sorted){ asort($users_array); } + return ($users_array); + } + + /** + * Converts a username (samAccountName) to a GUID + * + * @param string $username The username to query + * @return string + */ + public function username2guid($username) { + if (!$this->_bind){ return (false); } + if ($username === null){ return ("Missing compulsory field [username]"); } + + $filter = "samaccountname=" . $username; + $fields = array("objectGUID"); + $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields); + if (ldap_count_entries($this->_conn, $sr) > 0) { + $entry = @ldap_first_entry($this->_conn, $sr); + $guid = @ldap_get_values_len($this->_conn, $entry, 'objectGUID'); + $strGUID = $this->binary2text($guid[0]); + return ($strGUID); + } + else { + return (false); + } + } + + /** + * 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 user_move($username, $container) { + if (!$this->_bind){ 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->user_info($username, array("*")); + $dn = $userinfo[0]['distinguishedname'][0]; + $newrdn = "cn=" . $username; + $container = array_reverse($container); + $newcontainer = "ou=" . implode(",ou=",$container); + $newbasedn = strtolower($newcontainer) . "," . $this->_base_dn; + $result=@ldap_rename($this->_conn,$dn,$newrdn,$newbasedn,true); + if ($result !== true) { + return (false); + } + return (true); + } + + //***************************************************************************************************************** + // CONTACT FUNCTIONS + // * Still work to do in this area, and new functions to write + + /** + * Create a contact + * + * @param array $attributes The attributes to set to the contact + * @return bool + */ + public function contact_create($attributes){ + // Check for compulsory fields + if (!array_key_exists("display_name",$attributes)){ return ("Missing compulsory field [display_name]"); } + 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."); } + + // Translate the schema + $add=$this->adldap_schema($attributes); + + // Additional stuff only used for adding contacts + $add["cn"][0]=$attributes["display_name"]; + $add["objectclass"][0]="top"; + $add["objectclass"][1]="person"; + $add["objectclass"][2]="organizationalPerson"; + $add["objectclass"][3]="contact"; + if (!isset($attributes['exchange_hidefromlists'])) { + $add["msExchHideFromAddressLists"][0]="TRUE"; + } + + // Determine the container + $attributes["container"]=array_reverse($attributes["container"]); + $container="OU=".implode(",OU=",$attributes["container"]); + + // Add the entry + $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add); + if ($result!=true){ return (false); } + + return (true); + } + + /** + * Determine the list of groups a contact is a member of + * + * @param string $distinguisedname The full DN of a contact + * @param bool $recursive Recursively check groups + * @return array + */ + public function contact_groups($distinguishedname,$recursive=NULL){ + if ($distinguishedname===NULL){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it + if (!$this->_bind){ return (false); } + + // Search the directory for their information + $info=@$this->contact_info($distinguishedname,array("memberof","primarygroupid")); + $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our contact + + if ($recursive === true){ + foreach ($groups as $id => $group_name){ + $extra_groups=$this->recursive_groups($group_name); + $groups=array_merge($groups,$extra_groups); + } + } + + return ($groups); + } + + /** + * Get contact information + * + * @param string $distinguisedname The full DN of a contact + * @param array $fields Attributes to be returned + * @return array + */ + public function contact_info($distinguishedname,$fields=NULL){ + if ($distinguishedname===NULL){ return (false); } + if (!$this->_bind){ return (false); } + + $filter="distinguishedName=".$distinguishedname; + if ($fields===NULL){ $fields=array("distinguishedname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); } + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + if ($entries[0]['count'] >= 1) { + // AD does not return the primary group in the ldap query, we may need to fudge it + if ($this->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["primarygroupid"][0])){ + //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); + $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); + } else { + $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn; + } + } + + $entries[0]["memberof"]["count"]++; + return ($entries); + } + + /** + * Determine if a contact is a member of a group + * + * @param string $distinguisedname The full DN of a contact + * @param string $group The group name to query + * @param bool $recursive Recursively check groups + * @return bool + */ + public function contact_ingroup($distinguisedname,$group,$recursive=NULL){ + if ($distinguisedname===NULL){ return (false); } + if ($group===NULL){ return (false); } + if (!$this->_bind){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it + + // Get a list of the groups + $groups=$this->contact_groups($distinguisedname,array("memberof"),$recursive); + + // Return true if the specified group is in the group list + if (in_array($group,$groups)){ return (true); } + + return (false); + } + + /** + * Modify a contact + * + * @param string $distinguishedname The contact to query + * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes + * @return bool + */ + public function contact_modify($distinguishedname,$attributes){ + if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); } + + // Translate the update to the LDAP schema + $mod=$this->adldap_schema($attributes); + + // Check to see if this is an enabled status update + if (!$mod){ return (false); } + + // Do the update + $result=ldap_modify($this->_conn,$distinguishedname,$mod); + if ($result==false){ return (false); } + + return (true); + } + + /** + * Delete a contact + * + * @param string $distinguishedname The contact dn to delete (please be careful here!) + * @return array + */ + public function contact_delete($distinguishedname) { + $result = $this->dn_delete($distinguishedname); + if ($result!=true){ return (false); } + return (true); + } + + /** + * Return a list of all contacts + * + * @param bool $include_desc Include a description of a contact + * @param string $search The search parameters + * @param bool $sorted Whether to sort the results + * @return array + */ + public function all_contacts($include_desc = false, $search = "*", $sorted = true){ + if (!$this->_bind){ return (false); } + + // Perform the search and grab all their details + $filter = "(&(objectClass=contact)(cn=".$search."))"; + $fields=array("displayname","distinguishedname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + $users_array = array(); + for ($i=0; $i<$entries["count"]; $i++){ + if ($include_desc && strlen($entries[$i]["displayname"][0])>0){ + $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["displayname"][0]; + } elseif ($include_desc){ + $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["distinguishedname"][0]; + } else { + array_push($users_array, $entries[$i]["distinguishedname"][0]); + } + } + if ($sorted){ asort($users_array); } + return ($users_array); + } + + //***************************************************************************************************************** + // FOLDER FUNCTIONS + + /** + * Returns a folder listing for a specific OU + * See http://adldap.sourceforge.net/wiki/doku.php?id=api_folder_functions + * + * @param array $folder_name An array to the OU you wish to list. + * If set to NULL will list the root, strongly recommended to set + * $recursive to false in that instance! + * @param string $dn_type The type of record to list. This can be ADLDAP_FOLDER or ADLDAP_CONTAINER. + * @param bool $recursive Recursively search sub folders + * @param bool $type Specify a type of object to search for + * @return array + */ + public function folder_list($folder_name = NULL, $dn_type = ADLDAP_FOLDER, $recursive = NULL, $type = NULL) { + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it + if (!$this->_bind){ return (false); } + + $filter = '(&'; + if ($type !== NULL) { + switch ($type) { + case 'contact': + $filter .= '(objectClass=contact)'; + break; + case 'computer': + $filter .= '(objectClass=computer)'; + break; + case 'group': + $filter .= '(objectClass=group)'; + break; + case 'folder': + $filter .= '(objectClass=organizationalUnit)'; + break; + case 'container': + $filter .= '(objectClass=container)'; + break; + case 'domain': + $filter .= '(objectClass=builtinDomain)'; + break; + default: + $filter .= '(objectClass=user)'; + break; + } + } + else { + $filter .= '(objectClass=*)'; + } + // If the folder name is null then we will search the root level of AD + // This requires us to not have an OU= part, just the base_dn + $searchou = $this->_base_dn; + if (is_array($folder_name)) { + $ou = $dn_type . "=".implode("," . $dn_type . "=",$folder_name); + $filter .= '(!(distinguishedname=' . $ou . ',' . $this->_base_dn . ')))'; + $searchou = $ou . ',' . $this->_base_dn; + } + else { + $filter .= '(!(distinguishedname=' . $this->_base_dn . ')))'; + } + + if ($recursive === true) { + $sr=ldap_search($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname')); + $entries = @ldap_get_entries($this->_conn, $sr); + if (is_array($entries)) { + return $entries; + } + } + else { + $sr=ldap_list($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname')); + $entries = @ldap_get_entries($this->_conn, $sr); + if (is_array($entries)) { + return $entries; + } + } + + return false; + } + + //***************************************************************************************************************** + // COMPUTER FUNCTIONS + + /** + * Get information about a specific computer + * + * @param string $computer_name The name of the computer + * @param array $fields Attributes to return + * @return array + */ + public function computer_info($computer_name,$fields=NULL){ + if ($computer_name===NULL){ return (false); } + if (!$this->_bind){ return (false); } + + $filter="(&(objectClass=computer)(cn=".$computer_name."))"; + if ($fields===NULL){ $fields=array("memberof","cn","displayname","dnshostname","distinguishedname","objectcategory","operatingsystem","operatingsystemservicepack","operatingsystemversion"); } + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + return ($entries); + } + + /** + * Check if a computer is in a group + * + * @param string $computer_name The name of the computer + * @param string $group The group to check + * @param bool $recursive Whether to check recursively + * @return array + */ + public function computer_ingroup($computer_name,$group,$recursive=NULL){ + if ($computer_name===NULL){ return (false); } + if ($group===NULL){ return (false); } + if (!$this->_bind){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // use the default option if they haven't set it + + //get a list of the groups + $groups=$this->computer_groups($computer_name,array("memberof"),$recursive); + + //return true if the specified group is in the group list + if (in_array($group,$groups)){ return (true); } + + return (false); + } + + /** + * Get the groups a computer is in + * + * @param string $computer_name The name of the computer + * @param bool $recursive Whether to check recursively + * @return array + */ + public function computer_groups($computer_name,$recursive=NULL){ + if ($computer_name===NULL){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it + if (!$this->_bind){ return (false); } + + //search the directory for their information + $info=@$this->computer_info($computer_name,array("memberof","primarygroupid")); + $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our guy (unique usernames) + + if ($recursive === true){ + foreach ($groups as $id => $group_name){ + $extra_groups=$this->recursive_groups($group_name); + $groups=array_merge($groups,$extra_groups); + } + } + + return ($groups); + } + + //************************************************************************************************************ + // ORGANIZATIONAL UNIT FUNCTIONS + + /** + * Create an organizational unit + * + * @param array $attributes Default attributes of the ou + * @return bool + */ + public function ou_create($attributes){ + if (!is_array($attributes)){ return ("Attributes must be an array"); } + if (!array_key_exists("ou_name",$attributes)){ return ("Missing compulsory field [ou_name]"); } + if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } + if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } + $attributes["container"]=array_reverse($attributes["container"]); + + $add=array(); + $add["objectClass"] = "organizationalUnit"; + + $container="OU=".implode(",OU=",$attributes["container"]); + $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add); + if ($result!=true){ return (false); } + + return (true); + } + + //************************************************************************************************************ + // EXCHANGE FUNCTIONS + + /** + * Create an Exchange account + * + * @param string $username The username of the user to add the Exchange account to + * @param array $storagegroup The mailbox, Exchange Storage Group, for the user account, this must be a full CN + * If the storage group has a different base_dn to the adLDAP configuration, set it using $base_dn + * @param string $emailaddress The primary email address to add to this user + * @param string $mailnickname The mail nick name. If mail nickname is blank, the username will be used + * @param bool $usedefaults Indicates whether the store should use the default quota, rather than the per-mailbox quota. + * @param string $base_dn Specify an alternative base_dn for the Exchange storage group + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function exchange_create_mailbox($username, $storagegroup, $emailaddress, $mailnickname=NULL, $usedefaults=TRUE, $base_dn=NULL, $isGUID=false){ + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if ($storagegroup===NULL){ return ("Missing compulsory array [storagegroup]"); } + if (!is_array($storagegroup)){ return ("[storagegroup] must be an array"); } + if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); } + + if ($base_dn===NULL) { + $base_dn = $this->_base_dn; + } + + $container="CN=".implode(",CN=",$storagegroup); + + if ($mailnickname===NULL) { $mailnickname=$username; } + $mdbUseDefaults = $this->bool2str($usedefaults); + + $attributes = array( + 'exchange_homemdb'=>$container.",".$base_dn, + 'exchange_proxyaddress'=>'SMTP:' . $emailaddress, + 'exchange_mailnickname'=>$mailnickname, + 'exchange_usedefaults'=>$mdbUseDefaults + ); + $result = $this->user_modify($username,$attributes,$isGUID); + if ($result==false){ return (false); } + return (true); + } + + /** + * Add an X400 address to Exchange + * See http://tools.ietf.org/html/rfc1685 for more information. + * An X400 Address looks similar to this X400:c=US;a= ;p=Domain;o=Organization;s=Doe;g=John; + * + * @param string $username The username of the user to add the X400 to to + * @param string $country Country + * @param string $admd Administration Management Domain + * @param string $pdmd Private Management Domain (often your AD domain) + * @param string $org Organization + * @param string $surname Surname + * @param string $givenName Given name + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function exchange_add_X400($username, $country, $admd, $pdmd, $org, $surname, $givenname, $isGUID=false) { + if ($username===NULL){ return ("Missing compulsory field [username]"); } + + $proxyvalue = 'X400:'; + + // Find the dn of the user + $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID); + if ($user[0]["dn"]===NULL){ return (false); } + $user_dn=$user[0]["dn"]; + + // We do not have to demote an email address from the default so we can just add the new proxy address + $attributes['exchange_proxyaddress'] = $proxyvalue . 'c=' . $country . ';a=' . $admd . ';p=' . $pdmd . ';o=' . $org . ';s=' . $surname . ';g=' . $givenname . ';'; + + // Translate the update to the LDAP schema + $add=$this->adldap_schema($attributes); + + if (!$add){ return (false); } + + // Do the update + // Take out the @ to see any errors, usually this error might occur because the address already + // exists in the list of proxyAddresses + $result=@ldap_mod_add($this->_conn,$user_dn,$add); + if ($result==false){ return (false); } + + return (true); + } + + /** + * Add an address to Exchange + * + * @param string $username The username of the user to add the Exchange account to + * @param string $emailaddress The email address to add to this user + * @param bool $default Make this email address the default address, this is a bit more intensive as we have to demote any existing default addresses + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function exchange_add_address($username, $emailaddress, $default=FALSE, $isGUID=false) { + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } + + $proxyvalue = 'smtp:'; + if ($default === true) { + $proxyvalue = 'SMTP:'; + } + + // Find the dn of the user + $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID); + if ($user[0]["dn"]===NULL){ return (false); } + $user_dn=$user[0]["dn"]; + + // We need to scan existing proxy addresses and demote the default one + if (is_array($user[0]["proxyaddresses"]) && $default===true) { + $modaddresses = array(); + for ($i=0;$i_conn,$user_dn,$modaddresses); + if ($result==false){ return (false); } + + return (true); + } + else { + // We do not have to demote an email address from the default so we can just add the new proxy address + $attributes['exchange_proxyaddress'] = $proxyvalue . $emailaddress; + + // Translate the update to the LDAP schema + $add=$this->adldap_schema($attributes); + + if (!$add){ return (false); } + + // Do the update + // Take out the @ to see any errors, usually this error might occur because the address already + // exists in the list of proxyAddresses + $result=@ldap_mod_add($this->_conn,$user_dn,$add); + if ($result==false){ return (false); } + + return (true); + } + } + + /** + * Remove an address to Exchange + * If you remove a default address the account will no longer have a default, + * we recommend changing the default address first + * + * @param string $username The username of the user to add the Exchange account to + * @param string $emailaddress The email address to add to this user + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function exchange_del_address($username, $emailaddress, $isGUID=false) { + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } + + // Find the dn of the user + $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID); + if ($user[0]["dn"]===NULL){ return (false); } + $user_dn=$user[0]["dn"]; + + if (is_array($user[0]["proxyaddresses"])) { + $mod = array(); + for ($i=0;$i _conn,$user_dn,$mod); + if ($result==false){ return (false); } + + return (true); + } + else { + return (false); + } + } + /** + * Change the default address + * + * @param string $username The username of the user to add the Exchange account to + * @param string $emailaddress The email address to make default + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function exchange_primary_address($username, $emailaddress, $isGUID=false) { + if ($username===NULL){ return ("Missing compulsory field [username]"); } + if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); } + + // Find the dn of the user + $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID); + if ($user[0]["dn"]===NULL){ return (false); } + $user_dn=$user[0]["dn"]; + + if (is_array($user[0]["proxyaddresses"])) { + $modaddresses = array(); + for ($i=0;$i _conn,$user_dn,$modaddresses); + if ($result==false){ return (false); } + + return (true); + } + + } + + /** + * Mail enable a contact + * Allows email to be sent to them through Exchange + * + * @param string $distinguishedname The contact to mail enable + * @param string $emailaddress The email address to allow emails to be sent through + * @param string $mailnickname The mailnickname for the contact in Exchange. If NULL this will be set to the display name + * @return bool + */ + public function exchange_contact_mailenable($distinguishedname, $emailaddress, $mailnickname=NULL){ + if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); } + if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); } + + if ($mailnickname !== NULL) { + // Find the dn of the user + $user=$this->contact_info($distinguishedname,array("cn","displayname")); + if ($user[0]["displayname"]===NULL){ return (false); } + $mailnickname = $user[0]['displayname'][0]; + } + + $attributes = array("email"=>$emailaddress,"contact_email"=>"SMTP:" . $emailaddress,"exchange_proxyaddress"=>"SMTP:" . $emailaddress,"exchange_mailnickname"=>$mailnickname); + + // Translate the update to the LDAP schema + $mod=$this->adldap_schema($attributes); + + // Check to see if this is an enabled status update + if (!$mod){ return (false); } + + // Do the update + $result=ldap_modify($this->_conn,$distinguishedname,$mod); + if ($result==false){ return (false); } + + return (true); + } + + /** + * Returns a list of Exchange Servers in the ConfigurationNamingContext of the domain + * + * @param array $attributes An array of the AD attributes you wish to return + * @return array + */ + public function exchange_servers($attributes = array('cn','distinguishedname','serialnumber')) { + if (!$this->_bind){ return (false); } + + $configurationNamingContext = $this->get_root_dse(array('configurationnamingcontext')); + $sr = @ldap_search($this->_conn,$configurationNamingContext[0]['configurationnamingcontext'][0],'(&(objectCategory=msExchExchangeServer))',$attributes); + $entries = @ldap_get_entries($this->_conn, $sr); + return $entries; + } + + /** + * Returns a list of Storage Groups in Exchange for a given mail server + * + * @param string $exchangeServer The full DN of an Exchange server. You can use exchange_servers() to find the DN for your server + * @param array $attributes An array of the AD attributes you wish to return + * @param bool $recursive If enabled this will automatically query the databases within a storage group + * @return array + */ + public function exchange_storage_groups($exchangeServer, $attributes = array('cn','distinguishedname'), $recursive = NULL) { + if (!$this->_bind){ return (false); } + if ($exchangeServer===NULL){ return ("Missing compulsory field [exchangeServer]"); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } + + $filter = '(&(objectCategory=msExchStorageGroup))'; + $sr=@ldap_search($this->_conn, $exchangeServer, $filter, $attributes); + $entries = @ldap_get_entries($this->_conn, $sr); + + if ($recursive === true) { + for ($i=0; $i<$entries['count']; $i++) { + $entries[$i]['msexchprivatemdb'] = $this->exchange_storage_databases($entries[$i]['distinguishedname'][0]); + } + } + + return $entries; + } + + /** + * Returns a list of Databases within any given storage group in Exchange for a given mail server + * + * @param string $storageGroup The full DN of an Storage Group. You can use exchange_storage_groups() to find the DN + * @param array $attributes An array of the AD attributes you wish to return + * @return array + */ + public function exchange_storage_databases($storageGroup, $attributes = array('cn','distinguishedname','displayname')) { + if (!$this->_bind){ return (false); } + if ($storageGroup===NULL){ return ("Missing compulsory field [storageGroup]"); } + + $filter = '(&(objectCategory=msExchPrivateMDB))'; + $sr=@ldap_search($this->_conn, $storageGroup, $filter, $attributes); + $entries = @ldap_get_entries($this->_conn, $sr); + return $entries; + } + + //************************************************************************************************************ + // SERVER FUNCTIONS + + /** + * Find the Base DN of your domain controller + * + * @return string + */ + public function find_base_dn() { + $namingContext = $this->get_root_dse(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 get_root_dse($attributes = array("*", "+")) { + if (!$this->_bind){ return (false); } + + $sr = @ldap_read($this->_conn, NULL, 'objectClass=*', $attributes); + $entries = @ldap_get_entries($this->_conn, $sr); + return $entries; + } + + //************************************************************************************************************ + // UTILITY FUNCTIONS (Many of these functions are protected and can only be called from within the class) + + /** + * 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 get_last_error() { + return @ldap_error($this->_conn); + } + + /** + * Detect LDAP support in php + * + * @return bool + */ + protected function ldap_supported() { + if (!function_exists('ldap_connect')) { + return (false); + } + return (true); + } + + /** + * Schema + * + * @param array $attributes Attributes to be queried + * @return array + */ + protected 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 ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; } + if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; } + //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes? + if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; } + if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; } + if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; } + if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; } + if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; } + if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; } + if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; } + if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; } + if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; } + if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; } + if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format? + if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; } + if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; } + if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; } + if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; } + if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; } + if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName*** + if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; } + if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->encode_password($attributes["password"]); } + if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; } + if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; } + if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; } + if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; } + if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; } + if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; } + if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; } + if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; } + if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; } + if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; } + if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; } + + // Distribution List specific schema + if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; } + if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; } + + // Exchange Schema + if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; } + if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; } + if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; } + if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; } + if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; } + if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; } + if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; } + + // This schema is designed for contacts + if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; } + if ($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); + } + + /** + * Coping with AD not returning the primary group + * http://support.microsoft.com/?kbid=321360 + * + * For some reason it's not possible to search on primarygrouptoken=XXX + * If someone can show otherwise, I'd like to know about it :) + * this way is resource intensive and generally a pain in the @#%^ + * + * @deprecated deprecated since version 3.1, see get get_primary_group + * @param string $gid Group ID + * @return string + */ + protected function group_cn($gid){ + if ($gid===NULL){ return (false); } + $r=false; + + $filter="(&(objectCategory=group)(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP ."))"; + $fields=array("primarygrouptoken","samaccountname","distinguishedname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + for ($i=0; $i<$entries["count"]; $i++){ + if ($entries[$i]["primarygrouptoken"][0]==$gid){ + $r=$entries[$i]["distinguishedname"][0]; + $i=$entries["count"]; + } + } + + return ($r); + } + + /** + * Coping with AD not returning the primary group + * http://support.microsoft.com/?kbid=321360 + * + * This is a re-write based on code submitted by Bruce which prevents the + * need to search each security group to find the true primary group + * + * @param string $gid Group ID + * @param string $usersid User's Object SID + * @return string + */ + protected function get_primary_group($gid, $usersid){ + if ($gid===NULL || $usersid===NULL){ return (false); } + $r=false; + + $gsid = substr_replace($usersid,pack('V',$gid),strlen($usersid)-4,4); + $filter='(objectsid='.$this->getTextSID($gsid).')'; + $fields=array("samaccountname","distinguishedname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + return $entries[0]['distinguishedname'][0]; + } + + /** + * Convert a binary SID to a text SID + * + * @param string $binsid A Binary SID + * @return string + */ + protected function getTextSID($binsid) { + $hex_sid = bin2hex($binsid); + $rev = hexdec(substr($hex_sid, 0, 2)); + $subcount = hexdec(substr($hex_sid, 2, 2)); + $auth = hexdec(substr($hex_sid, 4, 12)); + $result = "$rev-$auth"; + + for ($x=0;$x < $subcount; $x++) { + $subauth[$x] = + hexdec($this->little_endian(substr($hex_sid, 16 + ($x * 8), 8))); + $result .= "-" . $subauth[$x]; + } + + // Cheat by tacking on the S- + return 'S-' . $result; + } + + /** + * Converts a little-endian hex number to one that hexdec() can convert + * + * @param string $hex A hex code + * @return string + */ + protected function little_endian($hex) { + $result = ''; + for ($x = strlen($hex) - 2; $x >= 0; $x = $x - 2) { + $result .= substr($hex, $x, 2); + } + return $result; + } + + /** + * Converts a binary attribute to a string + * + * @param string $bin A binary LDAP attribute + * @return string + */ + protected function binary2text($bin) { + $hex_guid = bin2hex($bin); + $hex_guid_to_guid_str = ''; + for($k = 1; $k <= 4; ++$k) { + $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2); + } + $hex_guid_to_guid_str .= '-'; + for($k = 1; $k <= 2; ++$k) { + $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2); + } + $hex_guid_to_guid_str .= '-'; + for($k = 1; $k <= 2; ++$k) { + $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2); + } + $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4); + $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20); + return strtoupper($hex_guid_to_guid_str); + } + + /** + * Converts a binary GUID to a string GUID + * + * @param string $binaryGuid The binary GUID attribute to convert + * @return string + */ + public function decodeGuid($binaryGuid) { + if ($binaryGuid === null){ return ("Missing compulsory field [binaryGuid]"); } + + $strGUID = $this->binary2text($binaryGuid); + return ($strGUID); + } + + /** + * Converts a string GUID to a hexdecimal value so it can be queried + * + * @param string $strGUID A string representation of a GUID + * @return string + */ + protected function strguid2hex($strGUID) { + $strGUID = str_replace('-', '', $strGUID); + + $octet_str = '\\' . substr($strGUID, 6, 2); + $octet_str .= '\\' . substr($strGUID, 4, 2); + $octet_str .= '\\' . substr($strGUID, 2, 2); + $octet_str .= '\\' . substr($strGUID, 0, 2); + $octet_str .= '\\' . substr($strGUID, 10, 2); + $octet_str .= '\\' . substr($strGUID, 8, 2); + $octet_str .= '\\' . substr($strGUID, 14, 2); + $octet_str .= '\\' . substr($strGUID, 12, 2); + //$octet_str .= '\\' . substr($strGUID, 16, strlen($strGUID)); + for ($i=16; $i<=(strlen($strGUID)-2); $i++) { + if (($i % 2) == 0) { + $octet_str .= '\\' . substr($strGUID, $i, 2); + } + } + + return $octet_str; + } + + /** + * 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 + */ + protected function user_dn($username,$isGUID=false){ + $user=$this->user_info($username,array("cn"),$isGUID); + if ($user[0]["dn"]===NULL){ return (false); } + $user_dn=$user[0]["dn"]; + return ($user_dn); + } + + /** + * Encode a password for transmission over LDAP + * + * @param string $password The password to encode + * @return string + */ + protected function encode_password($password){ + $password="\"".$password."\""; + $encoded=""; + for ($i=0; $i+ * @return string + */ + protected function ldap_slashes($str){ + return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e', + '"\\\\\".join("",unpack("H2","$1"))', + $str); + } + + /** + * Select a random domain controller from your domain controller array + * + * @return string + */ + protected function random_controller(){ + mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions + return ($this->_domain_controllers[array_rand($this->_domain_controllers)]); + } + + /** + * Account control options + * + * @param array $options The options to convert to int + * @return int + */ + protected function account_control($options){ + $val=0; + + if (is_array($options)){ + if (in_array("SCRIPT",$options)){ $val=$val+1; } + if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; } + if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; } + if (in_array("LOCKOUT",$options)){ $val=$val+16; } + if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; } + //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute. + //For information about how to set the permission programmatically, see the "Property flag descriptions" section. + if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; } + if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; } + if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; } + if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; } + if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; } + if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; } + if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; } + if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; } + if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; } + if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; } + if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; } + if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; } + if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; } + if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; } + if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; } + } + return ($val); + } + + /** + * Take an LDAP query and return the nice names, without all the LDAP prefixes (eg. CN, DN) + * + * @param array $groups + * @return array + */ + protected function nice_names($groups){ + + $group_array=array(); + for ($i=0; $i<$groups["count"]; $i++){ // For each group + $line=$groups[$i]; + + if (strlen($line)>0){ + // More presumptions, they're all prefixed with CN= + // so we ditch the first three characters and the group + // name goes up to the first comma + $bits=explode(",",$line); + $group_array[]=substr($bits[0],3,(strlen($bits[0])-3)); + } + } + return ($group_array); + } + + /** + * Delete a distinguished name from Active Directory + * You should never need to call this yourself, just use the wrapper functions user_delete and contact_delete + * + * @param string $dn The distinguished name to delete + * @return bool + */ + protected function dn_delete($dn){ + $result=ldap_delete($this->_conn, $dn); + if ($result!=true){ return (false); } + return (true); + } + + /** + * Convert a boolean value to a string + * You should never need to call this yourself + * + * @param bool $bool Boolean value + * @return string + */ + protected function bool2str($bool) { + return ($bool) ? 'TRUE' : 'FALSE'; + } + + /** + * 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); + } + } +} + +/** +* 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 {} + +?> \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..e0e4ebf --- /dev/null +++ b/readme.txt @@ -0,0 +1,171 @@ +=== Plugin Name === +Contributors: clifgriffin +Donate link: http://clifgriffin.com/donate/ +Tags: LDAP, authentication, login, active directory, adLDAP +Requires at least: 2.8 +Tested up to: 3.2.1 +Stable tag: 1.4.0.5.1 + +Integrating Wordpress with LDAP shouldn't be difficult. Now it isn't. Simple LDAP Login provides all of the features, none of the hassles. + +== Description == +Having a single login for every service is a must in large organizations. This plugin allows you to integrate Wordpress with LDAP quickly and easily. Like, really really easy. + +**Donate?** + +If you use this plugin, and find that it serves your organization's needs, please consider a donation. I have spent many unpaid hours supporting and updating this plugin. A plugin like this would easily retail for $200 or more. I offer it for free, with only the request that you consider giving back if you are financially capable of doing so. + +**Support** + +If you need support, I recommend you leave a comment on the appropriate post on my blog: +http://clifgriffin.com/2009/05/13/simple-ldap-login-13-for-wordpress/ + +**Special Requests** + +If you need a customization or change specific to your install, I am available for hire. Shoot me an e-mail: clifgriffin[at]gmail.com + += Features = + +* Supports Active Directory and OpenLDAP (and other directory systems which comply to the LDAP standard, such as OpenDS) +* Supports TLS +* Uses up-to-date methods for Wordpress authentication routines. +* Includes three login modes: +* * Normal Mode: Authenticates existing wordpress usernames against LDAP. This requires you to create all Wordpress accounts manually using the same user names as those in your LDAP directory. +* * Account Creation Mode 1: Creates Wordpress accounts automatically for any LDAP user. +* * Account Creation Mode 2: Creates Wordpress accounts automatically for LDAP users in a specific Group you specify. +* Intuitive control panel. + += Architecture = +Simple LDAP Login adds an authentication filter to Wordpress that authentication requests must pass. In doing so, it makes several decisions. + +* Can the provided credentials be authenticated against LDAP? +* * If so, is the username a valid WP username? +* * * If not, can we create a WP user? +* * * * If we can, does the user belong to the right (if any) group? +* * * * * If the user does, create the Wordpress user and log the user in. +* * If the username is already valid wordpress user, are they in the right group? +* * * Is so, log the user in. + +This is simply a high level overview. The actual logic the plugin employs is more complex, but hopefully this gives you an idea, philosophically, about how the plugin accomplishes what it does. If the plugin is unable to authenticate the user, it passes it down the chain to Wordpress. (Unless security mode is set to high, which will disable this functionality.) + +== Changelog == +**Version 1.4.0.5.1** +* I broke it. Sorry guys! :( +* Downgraded adLDAP as some referenced functions no longer exist. + +**Version 1.4.0.5** +* Updated adLDAP to version 4.x +* Fixed error in OpenLDAP group membership check +* As always TEST this first. Don't assume it worksÉI don't have a testing environment to ensure it will work correctly. + +**Version 1.4.0.4** +* Fixes nickname bug accidentally put back in in last version. (My bad!) + +**Version 1.4.0.3** +* Reverts bug introduced in 1.4.0.2 +* If you installed 1.4.0.2 and use OpenLDAP, please update as soon as possible and verify users cannot login with incorrect passwords (and vice versa). + +**Version 1.4.0.2 - Patches submitted by Jonas Genannt and Ilya Kozlov** +* Updates adLDAP to 3.3.2 +* Fixes issue with users in recursive OUs not being found. +* Fixes issues with different Base DN formats. +* NOTE: Please be catious in updating. As I don't have an OpenLDAP install, I am unable to independently confirm these fix the problems. If you have issues, revert to 1.4.0.1 and e-mail me: clifgriffin[at]gmail.com. Likewise, If you can confirm these changes are effective, also let me know. :) + +**Version 1.4.0.1** + +* Fix for e-mail exists issue with WP 3.0+ for LDAP installations that don't populate the e-mail address attribute. +* Shows actual error message from Wordpress upon failure. + +**Version 1.4** + +* First update in about a year. Thanks for your patience. +* Completely rewritten to support changes in Wordpress 2.8+. Now fully supports Wordpress 3.0. +* Much more manageable and efficient code structure. Less code repetition. +* Includes TLS support. +* Allows OpenLDAP users to specify an alternate LDAP attribute to use for logins for those not using UID. + +**Version 1.3.0.3** + +* Test form now implements wp_authenticate and uses the same routines as the actual login. This also means account creation and group membership are tested. +* Implemented stripslashes() to correct issue with some special characters such as a single quote and backslash. +* Wordpress account "admin" is now allowed to login using local password even when security mode is set to high. For safety. +* Made some minor wording changes to the admin panel. + +**Version 1.3.0.2.1** + +* Fixed case sensitivity issue that could result in multiple accounts. There may be lingering case insensitivity issues due to the get_userdatabylogin function being case-sensitive. We'll figure this out in due time. +* Sorry for posting two updates on the same day! + +**Version 1.3.0.2** + +* Fixes several tickets including role assignment, case sensitivity, and potential compatibility issues with other themes/plugins. +* Added security mode setting to allow security to be tightened. +* Changed auto created accounts to use a random password rather than the LDAP password given. +* Fixed error with the way announcements are displayed in the admin panel. +* More code clean up. + +**Version 1.3.0.1** + +* Never officially released. +* Contained code cleanup and some attempted fixes. + +**Version 1.3 Beta** + +* Support for both Active Directory and OpenLDAP. +* The ability to create wordpress users automatically upon login based on LDAP group membership OR by LDAP authentication alone. +* The ability to test domain settings straight from admin panel. +* Announcements pane that allows me to update you with fixes, cautions, new beta versions, or other important information. + +**Version 1.2.0.1** + +* Changed required user level for admin page to 10, Administrators only. + +**Version 1.2** + +* Implemented multiple domain controllers. +* Changed field sizes on admin page to be more user friendly. + +**Version 1.1** + +* Moved settings to administration pages under settings. +* Upgraded to latest version of adLDAP 2.1. +* Got rid of credentials. (They are not neccessary for the authenticate function in adLDAP!) +* Plugin is now upgrade proof. Settings are stored using Wordpress's setting functions. + +**Version 1.0** + +* Original release. + +== Installation == + +1. Use the WordPress plugin directory to install the plugin or upload the directory “simple-ldap-login” to the `/wp-content/plugins/` directory. +1. Activate the plugin through the 'Plugins' menu in WordPress +1. Immediately update the settings to those that best match your environment by going to Settings -> Simple LDAP Login +1. If you don’t get the settings right the first time…don’t fret! Just use your wordpress credentials…they will always work in low security mode. +1. Once you have the settings correct, you can change the security mode to High Security (if you so desire). +1. To make your life easier, consider using two different browsers (e.g., IE and Firefox) to do testing. Change settings in one. Test in the other. This will prevent any chance of being locked out. + +== Frequently Asked Questions == + += Other than Wordpress, what does my system require? = + +If you are using Active Directory, you will probably need PHP 5. This is because I'm using adLDAP 3.0 to do my Active Directory integration. As far as I know, the rest of the code should work with PHP 4. If you are unable to upgrade to PHP 5, find an older version of adLDAP (on sourceforge) and replace adLDAP.php with it. This should bypass the PHP 5 requirement. + +Additionally, PHP must be configured/compiled with ldap support! + += How do I know what the correct settings are? = + +I have tried to make the settings as self-explanatory as possible. If you are struggling figuring them out, you may need to speak with your LDAP administrator. I realize this is an obnoxious response, but there is no good, fail proof way to help you discover these settings. A good place to start, if you're feeling daring, might be to use ADSIEdit for Windows and Active Directory, or GQ for Linux and OpenLDAP. + += It's still not working, what other things can I try? = + +If you are confident your settings are correct and it still does not work, it may be time to check for port or firewall issues. If your LDAP server is running on a non-standard port or an obsolete version of the LDAP protocol you are going to have issues. Port 389 is the port this plugin, and nearly every other LDAP enabled software expects. They are also expecting protocol version 3. If you are using an old version of LDAP or running a non-standard port you may need to modify the code that the plugin runs or update your LDAP installation. + +Unfortunately I can't be relied upon to assist with these types of requests. I chose not to support these scenarios because they are infrequent and because they confuse everyone else. + += It's still not working! How can I get help? = +There are two ways. You can post a comment on my blog (http://clifgriffin.com/2009/05/13/simple-ldap-login-13-for-wordpress/) or you can e-mail me: me[at]clifgriffin.com. I'll do my best to get you up and running! + +== Screenshots == + +1. Easy to use admin panel. \ No newline at end of file diff --git a/screenshot-2.png b/screenshot-2.png new file mode 100644 index 0000000..762c6a6 Binary files /dev/null and b/screenshot-2.png differ -- cgit v1.2.3-54-g00ecf