diff --git a/public_html/deployment/classes/ldap.class.php b/public_html/deployment/classes/ldap.class.php index 0a1a757635759ccde8e3361a616a2b75b2b699df..36eca987ba1d1164b72a9c99a87189d03d24406a 100644 --- a/public_html/deployment/classes/ldap.class.php +++ b/public_html/deployment/classes/ldap.class.php @@ -1,33 +1,55 @@ <?php /** - * IML LDAP CONNECTOR FOR USER AUTHENTICATION + * + * IML LDAP CONNECTOR * + * 2022-02-22 ah added objGet(), sanitizeFilter() + * 2022-08-18 ah mask password (showing 4 chars only) + * 2022-08-22 ah mhash is deprecated + * 2022-08-26 ah fix verifyPassword + * * @author axel.hahn@iml.unibe.ch - * 07-2017 */ class imlldap { + // ---------------------------------------------------------------------- + // vars + // ---------------------------------------------------------------------- + + /** + * @var array options array for an ldap connection including some base settings and DNs + */ private $_aLdap = array( - 'server' => false, - 'port' => false, + 'server' => false, + 'port' => false, 'DnLdapUser' => false, // ldap rdn oder dn 'PwLdapUser' => false, - 'DnUserNode' => false, // ou=People... - 'DnAppNode' => false, // cn=AppGroup... + 'DnUserNode' => false, // ou=People... + 'DnAppNode' => false, // cn=AppGroup... 'protoVersion' => 3, - 'debugLevel' => 0, + 'debugLevel' => 0, ); + /** + * @var object current ldap connection + */ private $_ldapConn = false; + /** + * @var bool bind was done? + */ private $_ldapBind = false; var $bDebug = false; + // ---------------------------------------------------------------------- + // functions + // ---------------------------------------------------------------------- + /** * constructor * @param array $aConfig optional set ldap connection */ public function __construct($aConfig = array()) { - if (!function_exists("ldap_connect")){ + if (!function_exists("ldap_connect")) { die(__CLASS__ . " ERROR: php-ldap module is not installed on this server."); } if (count($aConfig)) { @@ -39,34 +61,38 @@ class imlldap { $this->close(); } - // ---------------------------------------------------------------------- // write debug text // ---------------------------------------------------------------------- - + /** * turn debug messages on; * if this detail level is not enough, set a value with key debugLevel in * ldap config array * @see setConfig() */ - public function debugOn(){ - $this->bDebug=true; - if($this->_aLdap['debugLevel']){ + public function debugOn() { + $this->bDebug = true; + if ($this->_aLdap['debugLevel']) { $this->_w(__FUNCTION__ . ' setting debug level ' . $this->_aLdap['debugLevel']); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, $this->_aLdap['debugLevel']); } } - + /** * turn debug messages off */ - public function debugOff(){ - $this->bDebug=false; + public function debugOff() { + $this->bDebug = false; ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 0); } - + /** + * write debug message if denugOn() was fired. + * + * @param string $sText message text + * @return boolean + */ private function _w($sText) { if (!$this->bDebug) { return false; @@ -75,6 +101,17 @@ class imlldap { return true; } + /** + * write last ldap error as debug + * + * @param string $sText message text + * @return boolean + */ + private function _wLdaperror($sText = '') { + $this->_w(($sText ? $sText . ' - ' : '' ) . 'last LDAP-ERROR: ' . ldap_error($this->_ldapConn)); + return true; + } + // ---------------------------------------------------------------------- // setup // ---------------------------------------------------------------------- @@ -96,12 +133,11 @@ class imlldap { if (is_array($aConfig)) { foreach (array_keys($this->_aLdap) as $sKey) { if (array_key_exists($sKey, $aConfig)) { - $this->_w(__FUNCTION__ . ' setting ldap '.$sKey.' = '. $aConfig[$sKey]); + $this->_w(__FUNCTION__ . ' setting ldap ' . $sKey . ' = ' . $aConfig[$sKey]); $this->_aLdap[$sKey] = $aConfig[$sKey]; } } } - } // ---------------------------------------------------------------------- @@ -138,15 +174,18 @@ class imlldap { $this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server'] . ':' . $this->_aLdap['port']); $this->_ldapConn = ldap_connect($this->_aLdap['server'], $this->_aLdap['port']); if (!$this->_ldapConn) { + $this->_wLdaperror(__FUNCTION__); die(__CLASS__ . " ERROR: ldap connect failed."); } $this->_w(__FUNCTION__ . ' OK, connected.'); - - if($this->_aLdap['protoVersion']){ + ldap_set_option($this->_ldapConn, LDAP_OPT_NETWORK_TIMEOUT, 3); + ldap_set_option($this->_ldapConn, LDAP_OPT_TIMELIMIT, 3); + + if ($this->_aLdap['protoVersion']) { $this->_w(__FUNCTION__ . ' setting protocol version .' . $this->_aLdap['protoVersion']); ldap_set_option($this->_ldapConn, LDAP_OPT_PROTOCOL_VERSION, $this->_aLdap['protoVersion']); } - + ldap_set_option($this->_ldapConn, LDAP_OPT_REFERRALS, 0); // for AD MS Windows } @@ -158,27 +197,31 @@ class imlldap { * @see connect() * @see unbind() * - * @param string $sUser username - * @param string $sPw password + * @param string $sUser optional: username (overrides _aLdap['DnLdapUser']) + * @param string $sPw optional: password (overrides _aLdap['PwLdapUser']) */ - public function bind($sUser, $sPw='') { - - if(!$this->_ldapConn){ + public function bind($sUser = '', $sPw = '') { + if(!$sUser){ + $sUser = $this->_aLdap['DnLdapUser']; + $sPw = $this->_aLdap['PwLdapUser']; + } + + if (!$this->_ldapConn) { $this->connect(); } - if($this->_ldapBind){ + if ($this->_ldapBind) { $this->unbind(); } - if(!$sUser){ + if (!$sUser) { $this->_w(__FUNCTION__ . ' ERROR: no user was set as first param.'); die("ERROR: no user was given to connect to ldap."); } - $this->_w(__FUNCTION__ . ' with user ' . $sUser. ' PW '.$sPw); - + $this->_w(__FUNCTION__ . ' with user ' . $sUser . ' PW ' . substr($sPw,0,4).'**********'); + $this->_ldapBind = @ldap_bind($this->_ldapConn, $sUser, $sPw); if (!$this->_ldapBind) { - $this->_w(__FUNCTION__ . ' failed with er error ' . ldap_error($this->_ldapConn)); + $this->_wLdaperror(__FUNCTION__); return false; } $this->_w(__FUNCTION__ . ' OK, successful.'); @@ -201,64 +244,151 @@ class imlldap { // ---------------------------------------------------------------------- // ldap highlevel functions // ---------------------------------------------------------------------- - + /** * check if a DN already exists; return is true/ false * @param string $sDn DN to check * @return boolean */ public function DnExists($sDn) { - $aData=$this->searchDn($sDn, '(&(objectclass=top))', $aAttributesToGet=array("*")); + $aData = $this->searchDn($sDn, '(&(objectclass=top))', array("*")); return is_array($aData); } + /** + * get simpler array from ldap_get_entries after ldap_search + * + * @param array $aRecord singel result item + * @return array + */ + public function normalizeSearchentry($aRecord) { + if (!is_array($aRecord) || !isset($aRecord['dn'])){ + return false; + } + $aItem = array(); + unset($aRecord['count']); + foreach ($aRecord as $sAttr => $aData) { + if (!is_integer($sAttr)) { + $value = $aData; + if (is_array($aData)) { + unset($aData['count']); + $bUseArray=count($aData)>1 || array_search($sAttr, array('hieradata', 'member', 'memberof', 'objectclass'))!==false; + if($bUseArray){ + sort($aData); + } + $value = $bUseArray ? $aData : $aData[0]; + } + $aItem[$sAttr] = $value; + } + } + return $aItem; + } + /** + * get simpler array from ldap_get_entries after ldap_search + * + * @param array $aRecord singel result item + * @return array + + public function normalizeSearchresult($aLdapSearchresult) { + if (!is_array($aLdapSearchresult)){ + return false; + } + $aReturn = array(); + unset($aRecord['count']); + foreach ($aLdapSearchresult as $aRecord) { + $aReturn[]=$this->normalizeSearchentry($aRecord); + } + return $aReturn; + } + */ + + /** + * sanitize value to put into a search filter + * WARNING: the implementation is incomplete! I replaces the first N ascii chars only + * + * source: https://www.rfc-editor.org/rfc/rfc4515.txt + * + * @example: + * $sCn = 'John Smith (john)'; + * $sSearchFilter = '(cn='.$oLdap->sanitizeFilter($sCn).')'; + * + * @param string $s value to sanitize + * @return string + */ + static public function sanitizeFilter($s){ + + // helper array to replace special chars + $aReplace=array(); + for($i=0; $i<65; $i++){ + $val=dechex($i); + if ($val<10){ + $val="0$val"; + } + $aReplace[chr($i)]='\\'.$val; + } + + $sReturn=$s; + $sReturn=str_replace(array_keys($aReplace), array_values($aReplace), $sReturn); + + return $sReturn; + } /** * search in ldap directory and get result as array * + * @param string $sDn DN to search for * @param string $sSearchFilter filter in ldap filter syntax * @param array $aAttributesToGet flat array of attributes to fetch + * @param boolean $bRecursive recusrive (uses ldap_search) or not (ldap_list) * @return array */ - public function searchDn($sDn, $sSearchFilter, $aAttributesToGet=array("*")) { + public function searchDn($sDn, $sSearchFilter='(objectclass=*)', $aAttributesToGet = array("*"), $bRecursive=true) { if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - - $this->_w(__FUNCTION__ . ' DN = '.$sDn . ' filter = '.$sSearchFilter); - - $oLdapSearch = ldap_search( - $this->_ldapConn, - $sDn, - $sSearchFilter, - $aAttributesToGet - ); + $this->_w(__FUNCTION__ . ' DN = ' . $sDn . ' filter = ' . $sSearchFilter . ' attributes = ' . print_r($aAttributesToGet, 1).' recursive = '.($bRecursive ? 'yes' : 'no' )); + + $oLdapSearch = $bRecursive + ? ldap_search($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet) + : ldap_list ($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet) + ; - $aItems = $oLdapSearch?ldap_get_entries($this->_ldapConn, $oLdapSearch):false; + if (!$oLdapSearch) { + $this->_w(__FUNCTION__ . " !!!ERROR!!! filter $sSearchFilter failed "); + return false; + } + $aItems = ldap_get_entries($this->_ldapConn, $oLdapSearch); + $this->_w(__FUNCTION__ . " count of returned items: ".count($aItems)); + // $this->_w(__FUNCTION__ . " <pre>".print_r($aItems,1).'</pre>'); return $aItems; } + /** - * search in ldap directory and get result as array + * search for entries in in ldap user node and get result as array * * @param string $sSearchFilter filter in ldap filter syntax * @param array $aAttributesToGet flat array of attributes to fetch + * @param bool $bRecursive flag: recursive search? default: true (=yes, recursive) + * * @return array */ - public function searchUser($sSearchFilter, $aAttributesToGet=array("*")) { + public function searchUser($sSearchFilter='', $aAttributesToGet = array("*"), $bRecursive=true) { + return $this->searchDn($this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet, $bRecursive); + /* if (!$this->_ldapBind) { $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); } - - $this->_w(__FUNCTION__ . ' DN = '.$this->_aLdap['DnUserNode'] . ' filter = '.$sSearchFilter); - + + $this->_w(__FUNCTION__ . ' DN = ' . $this->_aLdap['DnUserNode'] . ' filter = ' . $sSearchFilter); + $oLdapSearch = ldap_search( - $this->_ldapConn, - $this->_aLdap['DnUserNode'], - $sSearchFilter, - $aAttributesToGet + $this->_ldapConn, $this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet ); - $aItems = $oLdapSearch?ldap_get_entries($this->_ldapConn, $oLdapSearch):false; + $aItems = $oLdapSearch ? ldap_get_entries($this->_ldapConn, $oLdapSearch) : false; return $aItems; + */ } /** @@ -266,31 +396,35 @@ class imlldap { * It returns false if the user does not exist or is * not member of the group 'DnAppNode' (if it was set). * - * @param type $sUser user id or email to search + * @param type $sUser user id (uid) or email (mail) to search * @param type $aAttributesToGet i.e. array("ou", "sn", "vorname", "mail", "uid", "memberOf") - * @return boolean + * @return boolean|array */ - public function getUserInfo($sUser, $aAttributesToGet=array("*")) { + public function getUserInfo($sUser, $aAttributesToGet = array("*")) { if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } // generate search filter - $sSearchFilter = (strpos($sUser, '@'))?"(mail=$sUser)" : "(uid=$sUser)"; - if($this->_aLdap['DnAppNode']){ - $sSearchFilter.='(memberof='.$this->_aLdap['DnAppNode'] .')'; + $sSearchFilter = (strpos($sUser, '@')) ? "(mail=$sUser)" : "(uid=$sUser)"; + if ($this->_aLdap['DnAppNode']) { + $sSearchFilter .= '(memberof=' . $this->_aLdap['DnAppNode'] . ')'; } - $sSearchFilter='(&'.$sSearchFilter.')'; - + // $sSearchFilter .= '(memberof=*)'; + $sSearchFilter = '(&' . $sSearchFilter . ')'; $aItems = $this->searchUser($sSearchFilter, $aAttributesToGet); - if(is_array($aItems) && count($aItems)==2){ - $this->_w(__FUNCTION__ . ' OK: I got a single result: ' . print_r($aItems[0],1) ); + if (is_array($aItems) && count($aItems) == 2) { + $this->_w(__FUNCTION__ . ' OK: I got a single result: ' . print_r($aItems[0], 1)); return $aItems[0]; + } else { + $this->_w(__FUNCTION__ . ' ERROR: result is: <pre>' . print_r($aItems, 1) . '</pre>'); } - return false; + return false; } - + /** * search for a DN entry with the lookup user by a given username or * email address. It returns false if the user does not exist or is @@ -300,13 +434,14 @@ class imlldap { * @return string */ public function getUserDn($sUser) { - $this->_w(__FUNCTION__ . '('.$sUser.')'); - $aItem=$this->getUserInfo($sUser, array("dn")); - if(is_array($aItem) && array_key_exists('dn', $aItem)){ + $this->_w(__FUNCTION__ . '(' . $sUser . ')'); + $aItem = $this->getUserInfo($sUser, array("dn")); + if (is_array($aItem) && array_key_exists('dn', $aItem)) { $this->_w(__FUNCTION__ . ' OK: dn was found ' . $aItem['dn']); return $aItem['dn']; } - return false; + $this->_w(__FUNCTION__ . ' ERROR: dn was NOT found ' . print_r($aItem)); + return false; } /** @@ -317,18 +452,70 @@ class imlldap { * @param string $sPW password * @return boolean */ - public function setPassword($sUser, $sPW){ + public function setPassword($sUser, $sPW) { if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - $sDn=$this->getUserDn($sUser); - if ($sDn){ - return ldap_mod_replace ($this->_ldapConn, $sDn, array('userpassword' => "{MD5}".base64_encode(pack("H*",md5($sPW))))); + $sDn = $this->getUserDn($sUser); + if ($sDn) { + if (!ldap_mod_replace($this->_ldapConn, $sDn, array('userpassword' => "{MD5}" . base64_encode(pack("H*", md5($sPW)))))) { + $this->_wLdaperror(__FUNCTION__); + return false; + } else { + return true; + } } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser); return false; } - + + /** + * get NTLM hash from a string + * taken from https://secure.php.net/manual/en/ref.hash.php + * + * @param string $Input + * @return string + */ + private function _getNTLMHash($Input) { + // Convert the password from UTF8 to UTF16 (little endian) + $Input = iconv('UTF-8', 'UTF-16LE', $Input); + + // Encrypt it with the MD4 hash + $MD4Hash=hash('md4',$Input); + // Make it uppercase, not necessary, but it's common to do so with NTLM hashes + $NTLMHash = strtoupper($MD4Hash); + + // Return the result + return($NTLMHash); + } + + /** + * set a password for a given user for Samba + * this requires a ldap bind with master/ admin account + * see https://msdn.microsoft.com/en-us/library/cc223248.aspx + * see http://php.net/ldap-modify-batch - last examle + * see https://secure.php.net/manual/en/ref.hash.php + * + * @param string $sUser username or email + * @param string $sPW password + * @return boolean + */ + public function setPasswordSamba($sUser, $sPW) { + $sDn = $this->getUserDn($sUser); + if ($sDn) { + $sPwField = 'sambaNTPassword'; + $sPwValue = $this->_getNTLMHash($sPW); + return $this->objUpdate($sDn, array( + $sPwField => $sPwValue, + 'SambaPwdLastSet' => date('U'), + )); + } + $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser); + return false; + } + /** * update an ldap object * this requires a ldap bind with master/ admin account @@ -337,18 +524,20 @@ class imlldap { * @param string $aItem array of new ldap properties * @return boolean */ - public function objAdd($sDn, $aItem){ - $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); + public function objAdd($sDn, $aItem) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", <pre>['.print_r($aItem, 1).']</pre>)'); if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - if (!ldap_add($this->_ldapConn, $sDn, $aItem)){ - $this->_w(__FUNCTION__ . ' failed with er error ' . ldap_error($this->_ldapConn)); + if (!ldap_add($this->_ldapConn, $sDn, $aItem)) { + $this->_wLdaperror(__FUNCTION__); return false; } return true; } - + /** * update an ldap attribute * this requires a ldap bind with master/ admin account @@ -357,44 +546,81 @@ class imlldap { * @param string $aItem array of new ldap properties * @return boolean */ - public function objAddAttr($sDn, $aItem){ - $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); + public function objAddAttr($sDn, $aItem) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", [array])'); if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - if ($sDn && is_array($aItem)){ - $this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect'); - $this->_w(__FUNCTION__ . ' ldap_mod_add($this->_ldapConn, "'.$sDn.'", ' . print_r($aItem,1) . ')'); - if (!ldap_mod_add($this->_ldapConn, $sDn, $aItem)){ - $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); + if ($sDn && is_array($aItem)) { + $this->_w(__FUNCTION__ . ' ' . ($this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect')); + $this->_w(__FUNCTION__ . ' ldap_mod_add($this->_ldapConn, "' . $sDn . '", ' . print_r($aItem, 1) . ')'); + if (!ldap_mod_add($this->_ldapConn, $sDn, $aItem)) { + $this->_w(__FUNCTION__ . ' ldap_mod_add FAILED'); + $this->_wLdaperror(__FUNCTION__); return false; - } return true; - } - $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem,1)); + } + return true; + } + $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem, 1)); return false; } - + + /** + * read attributes from ldap node with given DN (using ldap_read) + * + * @param string $sDn DN to search for + * @param string $sSearchFilter filter in ldap filter syntax + * @param array $aAttributesToGet flat array of attributes to fetch + * @return array + */ + public function objGet($sDn, $sSearchFilter='(objectclass=*)', $aAttributesToGet = array("*")) { + + $this->_w(__FUNCTION__ . '("' . $sDn . '", filter = '.$sSearchFilter.', atttr= '.print_r($aAttributesToGet, 1).' )'); + if (!$this->_ldapBind) { + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } + } + + $oLdapResult = ldap_read($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet); + + if (!$oLdapResult) { + $this->_w(__FUNCTION__ . " !!!ERROR!!! DN or filter did not match."); + return false; + } + return ldap_get_entries($this->_ldapConn, $oLdapResult); + } + /** - * update an ldap object + * update an ldap object with given key-value array + * if the attribute (key) does not exist it will be created. * this requires a ldap bind with master/ admin account * * @param string $sDn full DN where to update the item * @param array $aItem updated entry * @return boolean */ - public function objUpdate($sDn, $aItem){ - $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); + public function objUpdate($sDn, $aItem) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", ' . print_r($aItem, 1) . ')'); if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - if ($sDn && is_array($aItem)){ - return ldap_mod_replace($this->_ldapConn, $sDn, $aItem); - } - $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) ' . print_r($aItem,1)); + if ($sDn && is_array($aItem)) { + if (!ldap_mod_replace($this->_ldapConn, $sDn, $aItem)) { + $this->_w(__FUNCTION__ . ' ldap_mod_replace FAILED'); + $this->_wLdaperror(__FUNCTION__); + return false; + } + return true; + } + $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) ' . print_r($aItem, 1)); return false; } - /** * delete an ldap object * this requires a ldap bind with master/ admin account @@ -402,22 +628,24 @@ class imlldap { * @param string $sDn full DN to remove * @return boolean */ - public function objDelete($sDn){ - $this->_w(__FUNCTION__ . '("'.$sDn.'")'); + public function objDelete($sDn) { + $this->_w(__FUNCTION__ . '("' . $sDn . '")'); if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - - if ($sDn){ - if (!ldap_delete($this->_ldapConn, $sDn)){ - $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); + + if ($sDn) { + if (!ldap_delete($this->_ldapConn, $sDn)) { + $this->_wLdaperror(__FUNCTION__); return false; } return true; - } + } $this->_w(__FUNCTION__ . ' missing parameter for DN'); return false; } - + /** * delete attributes of an ldap object * this requires a ldap bind with master/ admin account @@ -428,23 +656,91 @@ class imlldap { * @param string $aItem item to remove * @return boolean */ - public function objDeleteAttr($sDn, $aItem){ - $this->_w(__FUNCTION__ . '("'.$sDn.'", [array])'); + public function objDeleteAttr($sDn, $aItem) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", [array])'); if (!$this->_ldapBind) { - $this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']); + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } } - if ($sDn && is_array($aItem)){ - $this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect'); - $this->_w(__FUNCTION__ . ' ldap_mod_del($this->_ldapConn, "'.$sDn.'", ' . print_r($aItem,1) . ')'); - if (!ldap_mod_del($this->_ldapConn, $sDn, $aItem)){ - $this->_w(__FUNCTION__ . ' ERROR: ' . ldap_error($this->_ldapConn)); + if ($sDn && is_array($aItem)) { + $this->_w(__FUNCTION__ . ' ' . ($this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect')); + $this->_w(__FUNCTION__ . ' ldap_mod_del($this->_ldapConn, "' . $sDn . '", ' . print_r($aItem, 1) . ')'); + if (!ldap_mod_del($this->_ldapConn, $sDn, $aItem)) { + $this->_wLdaperror(__FUNCTION__); return false; } return true; - } - $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem,1)); + } + $this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not an array ' . print_r($aItem, 1)); return false; } - + + /** + * check if an attribute exists in a DN + * + * @param string $sDn DN + * @param string $sAttribute attribute name to check + * @param string $sAttrValue value to check + * @return boolean + */ + public function objectAttributeExists($sDn, $sAttribute) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '")'); + + if (!$this->_ldapBind) { + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } + } + $aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute)); + $return = (is_array($aData) && isset($aData[0][strtolower($sAttribute)])); + $this->_w(__FUNCTION__ . '(...) returns ' . ($return ? 'true' : 'false')); + return $return; + } + + /** + * check if an attribute and value exist in a DN + * + * @param string $sDn DN + * @param string $sAttribute attribute name to check + * @param string $sAttrValue value to check + * @return boolean + */ + public function objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")'); + + if (!$this->_ldapBind) { + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } + } + $aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute)); + $return = (is_array($aData) && isset($aData[0][strtolower($sAttribute)]) && array_search($sAttrValue, $aData[0][strtolower($sAttribute)]) !== false); + $this->_w(__FUNCTION__ . '(...) returns ' . ($return ? 'true' : 'false')); + return $return; + } + + /** + * check an attribute and value; it will be created if it does not exist + * this requires a ldap bind with master/ admin account + * + * @param string $sDn dn to update + * @param string $sAttribute attribute name to check + * @param string $sAttrValue value to check + * @return boolean + */ + public function objectAttributeAndValueMustExist($sDn, $sAttribute, $sAttrValue) { + $this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")'); + // return if it already exists + if ($this->objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue)) { + return true; + } + + // create it + $this->_w(__FUNCTION__ . " create $sAttribute = $sAttrValue"); + $return = $this->objAddAttr($sDn, array($sAttribute => $sAttrValue)); + return $return; + } + /** * create a new user item * this requires a ldap bind with master/ admin account @@ -453,18 +749,18 @@ class imlldap { * @param string $sDn optional DN where to create the user * @return boolean */ - public function userAdd($aItem, $sDn=false){ - if (!$sDn){ - $sDn='cn='.$aItem['cn'].','.$this->_aLdap['DnUserNode']; + public function userAdd($aItem, $sDn = false) { + if (!$sDn) { + $sDn = 'cn=' . $aItem['cn'] . ',' . $this->_aLdap['DnUserNode']; } - $this->_w(__FUNCTION__ . '([array], "'.$sDn.'")'); - if ($sDn){ + $this->_w(__FUNCTION__ . '([array], "' . $sDn . '")'); + if ($sDn) { return $this->objAdd($sDn, $aItem); - } - $this->_w(__FUNCTION__ . ' node dn where to put the user was not found; set a value DnUserNode in ldap config or set it as 2nd parameter ' . print_r($aItem,1)); + } + $this->_w(__FUNCTION__ . ' node dn where to put the user was not found; set a value DnUserNode in ldap config or set it as 2nd parameter ' . print_r($aItem, 1)); return false; } - + /** * delete a user * this requires a ldap bind with master/ admin account @@ -473,41 +769,50 @@ class imlldap { * @param string $sPW new password to set * @return boolean */ - public function userDelete($sUserDn){ - $this->_w(__FUNCTION__ . '('.$sUserDn.')'); + public function userDelete($sUserDn) { + $this->_w(__FUNCTION__ . '(' . $sUserDn . ')'); return $this->objDelete($sUserDn); } - + /** * update an ldap object * this requires a ldap bind with master/ admin account * - * @param string $sUser user to update - * @param string $sPW new password to set + * @param array $aItem new user data to update * @return boolean */ - public function userUpdate($aItem){ + public function userUpdate($aItem) { $this->_w(__FUNCTION__ . '([array])'); - $sDn=$this->getUserDn($aItem['uid']); - if ($sDn){ + $sDn = $this->getUserDn($aItem['uid']); + if ($sDn) { + if (array_key_exists('cn', $aItem)) { + $this->_w(__FUNCTION__ . ' deleting cn entry.'); + unset($aItem['cn']); + } return $this->objUpdate($sDn, $aItem); - } + } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sDn); return false; } + /** * verify user and password * @param string $sUser username or email * @param string $sPW password * @return boolean */ - public function verifyPassword($sUser, $sPW){ - $sDn=$this->getUserDn($sUser); - if ($sDn){ + public function verifyPassword($sUser, $sPW) { + $sDn = $this->getUserDn($sUser); + if ($sDn) { return $this->bind($sDn, $sPW); + /* + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + return false; + } + */ } $this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser); return false; } - + }