diff --git a/plugins/libkolab/lib/kolab_ldap.php b/plugins/libkolab/lib/kolab_ldap.php
index dc8cf1eb..6d5675c1 100644
--- a/plugins/libkolab/lib/kolab_ldap.php
+++ b/plugins/libkolab/lib/kolab_ldap.php
@@ -1,610 +1,610 @@
 <?php
 
 /**
  * Kolab Authentication and User Base
  *
  * @author Aleksander Machniak <machniak@kolabsys.com>
  *
  * Copyright (C) 2011-2019, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 /**
  * Wrapper class for rcube_ldap_generic
  */
 class kolab_ldap extends rcube_ldap_generic
 {
     public $ready = false;
 
     private $conf = [];
     private $debug = false;
     private $fieldmap = [];
     private $parse_replaces = [];
 
     public function __construct($p)
     {
         $rcmail = rcube::get_instance();
 
         $this->conf = $p;
         $this->conf['kolab_auth_user_displayname'] = $rcmail->config->get('kolab_auth_user_displayname', '{name}');
 
         $this->fieldmap = $p['fieldmap'];
         $this->fieldmap['uid'] = 'uid';
 
         $p['attributes'] = array_values($this->fieldmap);
         $p['debug']      = (bool) $rcmail->config->get('ldap_debug');
 
         if ($cache_type = $rcmail->config->get('ldap_cache', 'db')) {
             $cache_ttl   = $rcmail->config->get('ldap_cache_ttl', '10m');
             $this->cache = $rcmail->get_cache('LDAP.kolab_cache', $cache_type, $cache_ttl);
         }
 
         $this->debug = $p['debug'];
 
         // Connect to the server (with bind)
         parent::__construct($p);
         $this->_connect();
 
         $rcmail->add_shutdown_function([$this, 'close']);
     }
 
     /**
     * Establish a connection to the LDAP server
     */
     private function _connect()
     {
         // try to connect + bind for every host configured
         // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable
         // see http://www.php.net/manual/en/function.ldap-connect.php
         foreach ((array)$this->config['hosts'] as $host) {
             // skip host if connection failed
             if (!$this->connect($host)) {
                 continue;
             }
 
             $bind_pass      = $this->config['bind_pass'] ?? null;
             $bind_user      = $this->config['bind_user'] ?? null;
             $bind_dn        = $this->config['bind_dn'];
             $base_dn        = $this->config['base_dn'];
             $groups_base_dn = $this->config['groups']['base_dn'] ?: $base_dn;
 
             // User specific access, generate the proper values to use.
             if ($this->config['user_specific']) {
                 $rcube = rcube::get_instance();
 
                 // No password set, use the session password
                 if (empty($bind_pass)) {
                     $bind_pass = $rcube->get_user_password();
                 }
 
                 $u = null;
                 // Get the pieces needed for variable replacement.
                 if ($fu = ($rcube->get_user_email() ?: ($this->config['username'] ?? null))) {
                     [$u, $d] = explode('@', $fu);
                 } else {
                     $d = $this->config['mail_domain'] ?? null;
                 }
 
                 $dc = 'dc=' . strtr($d, ['.' => ',dc=']); // hierarchal domain string
 
                 // resolve $dc through LDAP
                 if (!empty($this->config['domain_filter']) && !empty($this->config['search_bind_dn'])) {
                     $this->bind($this->config['search_bind_dn'], $this->config['search_bind_pw']);
                     $dc = $this->domain_root_dn($d);
                 }
 
                 $replaces = ['%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u];
 
                 // Search for the dn to use to authenticate
                 if (($this->config['search_base_dn'] ?? false) && ($this->config['search_filter'] ?? false)
                     && (strstr($bind_dn, '%dn') || strstr($base_dn, '%dn') || strstr($groups_base_dn, '%dn'))
                 ) {
                     $search_attribs = ['uid'];
                     if ($search_bind_attrib = (array) $this->config['search_bind_attrib']) {
                         foreach ($search_bind_attrib as $r => $attr) {
                             $search_attribs[] = $attr;
                             $replaces[$r] = '';
                         }
                     }
 
                     $search_bind_dn = strtr($this->config['search_bind_dn'], $replaces);
                     $search_base_dn = strtr($this->config['search_base_dn'], $replaces);
                     $search_filter  = strtr($this->config['search_filter'], $replaces);
 
                     $cache_key = 'DN.' . md5("$host:$search_bind_dn:$search_base_dn:$search_filter:" . $this->config['search_bind_pw']);
 
                     if ($this->cache && ($dn = $this->cache->get($cache_key))) {
                         $replaces['%dn'] = $dn;
                     } else {
                         $ldap = $this;
                         if (!empty($search_bind_dn) && !empty($this->config['search_bind_pw'])) {
                             // To protect from "Critical extension is unavailable" error
                             // we need to use a separate LDAP connection
                             if (!empty($this->config['vlv'])) {
                                 $ldap = new rcube_ldap_generic($this->config);
                                 $ldap->config_set(['cache' => $this->cache, 'debug' => $this->debug]);
                                 if (!$ldap->connect($host)) {
                                     continue;
                                 }
                             }
 
                             if (!$ldap->bind($search_bind_dn, $this->config['search_bind_pw'])) {
                                 continue;  // bind failed, try next host
                             }
                         }
 
                         $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs);
                         if ($res) {
                             $res->rewind();
                             $replaces['%dn'] = key($res->entries(true));
 
                             // add more replacements from 'search_bind_attrib' config
                             if ($search_bind_attrib) {
                                 $res = $res->current();
                                 foreach ($search_bind_attrib as $r => $attr) {
                                     $replaces[$r] = $res[$attr][0];
                                 }
                             }
                         }
 
                         if ($ldap != $this) {
                             $ldap->close();
                         }
                     }
 
                     // DN not found
                     if (empty($replaces['%dn'])) {
                         if (!empty($this->config['search_dn_default'])) {
                             $replaces['%dn'] = $this->config['search_dn_default'];
                         } else {
                             rcube::raise_error([
                                 'code' => 100, 'type' => 'ldap',
                                 'file' => __FILE__, 'line' => __LINE__,
                                 'message' => "DN not found using LDAP search."], true);
                             continue;
                         }
                     }
 
                     if ($this->cache && !empty($replaces['%dn'])) {
                         $this->cache->set($cache_key, $replaces['%dn']);
                     }
                 }
 
                 // Replace the bind_dn and base_dn variables.
                 $bind_dn        = strtr($bind_dn, $replaces);
                 $base_dn        = strtr($base_dn, $replaces);
                 $groups_base_dn = strtr($groups_base_dn, $replaces);
 
                 // replace placeholders in filter settings
                 if (!empty($this->config['filter'])) {
                     $this->config['filter'] = strtr($this->config['filter'], $replaces);
                 }
 
                 foreach (['base_dn', 'filter', 'member_filter'] as $k) {
                     if (!empty($this->config['groups'][$k])) {
                         $this->config['groups'][$k] = strtr($this->config['groups'][$k], $replaces);
                     }
                 }
 
                 if (empty($bind_user)) {
                     $bind_user = $u;
                 }
             }
 
             if (empty($bind_pass)) {
                 $this->ready = true;
             } else {
                 if (!empty($this->config['auth_cid'])) {
                     $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_dn);
                 } elseif (!empty($bind_dn)) {
                     $this->ready = $this->bind($bind_dn, $bind_pass);
                 } else {
                     $this->ready = $this->sasl_bind($bind_user, $bind_pass);
                 }
             }
 
             // connection established, we're done here
             if ($this->ready) {
                 break;
             }
 
         }  // end foreach hosts
 
-        if (!is_resource($this->conn)) {
+        if (empty($this->conn)) {
             rcube::raise_error(['code' => 100, 'type' => 'ldap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Could not connect to any LDAP server"], true);
 
             $this->ready = false;
         }
 
         return $this->ready;
     }
 
     /**
      * Fetches user data from LDAP addressbook
      */
     public function get_user_record($user, $host)
     {
         $rcmail  = rcube::get_instance();
         $filter  = $rcmail->config->get('kolab_auth_filter');
         $filter  = $this->parse_vars($filter, $user, $host);
         $base_dn = $this->parse_vars($this->config['base_dn'], $user, $host);
         $scope   = $this->config['scope'];
 
         // @TODO: print error if filter is empty
 
         // get record
         if ($result = parent::search($base_dn, $filter, $scope, $this->attributes)) {
             if ($result->count() == 1) {
                 $entries = $result->entries(true);
                 $dn      = key($entries);
                 $entry   = array_pop($entries);
                 $entry   = $this->field_mapping($dn, $entry);
 
                 return $entry;
             }
         }
     }
 
     /**
      * Fetches user data from LDAP addressbook
      */
     public function get_user_groups($dn, $user, $host)
     {
         if (empty($dn) || empty($this->config['groups'])) {
             return [];
         }
 
         $base_dn     = $this->parse_vars($this->config['groups']['base_dn'], $user, $host);
         $name_attr   = $this->config['groups']['name_attr'] ? $this->config['groups']['name_attr'] : 'cn';
         $member_attr = $this->get_group_member_attr();
         $filter      = "(member=$dn)(uniqueMember=$dn)";
 
         if ($member_attr != 'member' && $member_attr != 'uniqueMember') {
             $filter .= "($member_attr=$dn)";
         }
         $filter = strtr("(|$filter)", ["\\" => "\\\\"]);
 
         $result = parent::search($base_dn, $filter, 'sub', ['dn', $name_attr]);
 
         if (!$result) {
             return [];
         }
 
         $groups = [];
         foreach ($result as $entry) {
             $dn    = $entry['dn'];
             $entry = rcube_ldap_generic::normalize_entry($entry);
 
             $groups[$dn] = $entry[$name_attr];
         }
 
         return $groups;
     }
 
     /**
      * Get a specific LDAP record
      *
      * @param string $dn DN
      *
      * @return array|null Record data
      */
     public function get_record($dn)
     {
         if (!$this->ready) {
             return null;
         }
 
         if ($rec = $this->get_entry($dn, $this->attributes)) {
             $rec = rcube_ldap_generic::normalize_entry($rec);
             $rec = $this->field_mapping($dn, $rec);
         }
 
         return $rec;
     }
 
     /**
      * Replace LDAP record data items
      *
      * @param string $dn    DN
      * @param array  $entry LDAP entry
      *
      * return bool True on success, False on failure
      */
     public function replace($dn, $entry)
     {
         // fields mapping
         foreach ($this->fieldmap as $field => $attr) {
             if (array_key_exists($field, $entry)) {
                 $entry[$attr] = $entry[$field];
                 if ($attr != $field) {
                     unset($entry[$field]);
                 }
             }
         }
 
         return $this->mod_replace($dn, $entry);
     }
 
     /**
      * Search records (simplified version of rcube_ldap::search)
      *
      * @param mixed   $fields   The field name or array of field names to search in
      * @param string  $value    Search value
      * @param int     $mode     Matching mode:
      *                          0 - partial (*abc*),
      *                          1 - strict (=),
      *                          2 - prefix (abc*)
      * @param array   $required List of fields that cannot be empty
      * @param int     $limit    Number of records
      * @param int     $count    Returns the number of records found
      *
      * @return array List of LDAP records found
      */
     public function dosearch($fields, $value, $mode = 1, $required = [], $limit = 0, &$count = 0)
     {
         if (empty($fields)) {
             return [];
         }
 
         $mode  = intval($mode);
 
         // try to resolve field names into ldap attributes
         $fieldmap = $this->fieldmap;
         $attrs = array_map(function ($f) use ($fieldmap) {
             return array_key_exists($f, $fieldmap) ? $fieldmap[$f] : $f;
         }, (array)$fields);
 
         // compose a full-text-search-like filter
         if (count($attrs) > 1 || $mode != 1) {
             $filter = self::fulltext_search_filter($value, $attrs, $mode);
         }
         // direct search
         else {
             $field  = $attrs[0];
             $filter = "($field=" . self::quote_string($value) . ")";
         }
 
         // add required (non empty) fields filter
         $req_filter = '';
 
         foreach ((array)$required as $field) {
             $attr = array_key_exists($field, $this->fieldmap) ? $this->fieldmap[$field] : $field;
 
             // only add if required field is not already in search filter
             if (!in_array($attr, $attrs)) {
                 $req_filter .= "($attr=*)";
             }
         }
 
         if (!empty($req_filter)) {
             $filter = '(&' . $req_filter . $filter . ')';
         }
 
         // avoid double-wildcard if $value is empty
         $filter = preg_replace('/\*+/', '*', $filter);
 
         // add general filter to query
         if (!empty($this->config['filter'])) {
             $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->config['filter']) . ')' . $filter . ')';
         }
 
         $base_dn = $this->parse_vars($this->config['base_dn']);
         $scope   = $this->config['scope'];
         $attrs   = array_values($this->fieldmap);
         $list    = [];
 
         if ($result = $this->search($base_dn, $filter, $scope, $attrs)) {
             $count = $result->count();
             $i = 0;
             foreach ($result as $entry) {
                 if ($limit && $limit <= $i) {
                     break;
                 }
 
                 $dn        = $entry['dn'];
                 $entry     = rcube_ldap_generic::normalize_entry($entry);
                 $list[$dn] = $this->field_mapping($dn, $entry);
                 $i++;
             }
         }
 
         return $list;
     }
 
     /**
      * Set filter used in search()
      */
     public function set_filter($filter)
     {
         $this->config['filter'] = $filter;
     }
 
     /**
      * Maps LDAP attributes to defined fields
      */
     protected function field_mapping($dn, $entry)
     {
         $entry['dn'] = $dn;
 
         // fields mapping
         foreach ($this->fieldmap as $field => $attr) {
             // $entry might be indexed by lower-case attribute names
             $attr_lc = strtolower($attr);
             if (isset($entry[$attr_lc])) {
                 $entry[$field] = $entry[$attr_lc];
             } elseif (isset($entry[$attr])) {
                 $entry[$field] = $entry[$attr];
             }
         }
 
         // compose display name according to config
         if (empty($this->fieldmap['displayname'])) {
             $entry['displayname'] = rcube_addressbook::compose_search_name(
                 $entry,
                 $entry['email'],
                 $entry['name'] ?? null,
                 $this->conf['kolab_auth_user_displayname']
             );
         }
 
         return $entry;
     }
 
     /**
      * Detects group member attribute name
      */
     private function get_group_member_attr($object_classes = [])
     {
         if (empty($object_classes)) {
             $object_classes = $this->config['groups']['object_classes'];
         }
         if (!empty($object_classes)) {
             foreach ((array)$object_classes as $oc) {
                 switch (strtolower($oc)) {
                     case 'group':
                     case 'groupofnames':
                     case 'kolabgroupofnames':
                         $member_attr = 'member';
                         break;
 
                     case 'groupofuniquenames':
                     case 'kolabgroupofuniquenames':
                         $member_attr = 'uniqueMember';
                         break;
                 }
             }
         }
 
         if (!empty($member_attr)) {
             return $member_attr;
         }
 
         if (!empty($this->config['groups']['member_attr'])) {
             return $this->config['groups']['member_attr'];
         }
 
         return 'member';
     }
 
     /**
      * Prepares filter query for LDAP search
      */
     public function parse_vars($str, $user = null, $host = null)
     {
         // When authenticating user $user is always set
         // if not set it means we use this LDAP object for other
         // purposes, e.g. kolab_delegation, then username with
         // correct domain is in a session
         if (!$user && !empty($_SESSION['username'])) {
             $user = $_SESSION['username'];
         }
 
         $dc = null;
         if (isset($this->icache[$user])) {
             [$user, $dc] = $this->icache[$user];
         } else {
             $orig_user = $user;
             $domain = null;
             $rcmail = rcube::get_instance();
 
             // get default domain
             if ($username_domain = $rcmail->config->get('username_domain')) {
                 if ($host && is_array($username_domain) && isset($username_domain[$host])) {
                     $domain = rcube_utils::parse_host($username_domain[$host], $host);
                 } elseif (is_string($username_domain)) {
                     $domain = rcube_utils::parse_host($username_domain, $host);
                 }
             }
 
             // realmed username (with domain)
             if ($user && strpos($user, '@')) {
                 [$usr, $dom] = explode('@', $user);
 
                 // unrealm domain, user login can contain a domain alias
                 if ($dom != $domain && ($dc = $this->domain_root_dn($dom))) {
                     // @FIXME: we should replace domain in $user, I suppose
                 }
             } elseif ($domain) {
                 $user .= '@' . $domain;
             }
 
             $this->icache[$orig_user] = [$user, $dc];
         }
 
         // replace variables in filter
         [$u, $d] = explode('@', $user);
 
         // hierarchal domain string
         if (empty($dc)) {
             $dc = 'dc=' . strtr($d, ['.' => ',dc=']);
         }
 
         $replaces = ['%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u];
 
         $this->parse_replaces = $replaces;
 
         return strtr($str, $replaces);
     }
 
     /**
      * Returns variables used for replacement in (last) parse_vars() call
      *
      * @return array Variable-value hash array
      */
     public function get_parse_vars()
     {
         return $this->parse_replaces;
     }
 
     /**
      * Register additional fields
      */
     public function extend_fieldmap($map)
     {
         foreach ((array)$map as $name => $attr) {
             if (!in_array($attr, $this->attributes)) {
                 $this->attributes[]    = $attr;
                 $this->fieldmap[$name] = $attr;
             }
         }
     }
 
     /**
      * HTML-safe DN string encoding
      *
      * @param string $str DN string
      *
      * @return string Encoded HTML identifier string
      */
     public static function dn_encode($str)
     {
         return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
     }
 
     /**
      * Decodes DN string encoded with _dn_encode()
      *
      * @param string $str Encoded HTML identifier string
      *
      * @return string DN string
      */
     public static function dn_decode($str)
     {
         $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT);
         return base64_decode($str);
     }
 }