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;
     }
-    
+
 }