/** * Execute LDAP search * * @param string $base_dn Base DN to use for searching * @param string $filter Filter string to query * @param string $scope The LDAP scope (list|sub|base) * @param array $attrs List of entry attributes to read * @param array $prop Hash array with query configuration properties: * - sort: array of sort attributes (has to be in sync with the VLV index) * - search: search string used for VLV controls * @param bool $count_only Set to true if only entry count is requested * * @return mixed Net_LDAP3_Result object or number of entries (if $count_only=true) or False on failure */ public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $props = array(), $count_only = false) { if (!$this->conn) { $this->_debug("No active connection for " . __CLASS__ . "::" . __FUNCTION__); return false; } // make sure attributes list is not empty if (empty($attrs)) { $attrs = array('dn'); } // make sure filter is not empty if (empty($filter)) { $filter = '(objectclass=*)'; } $this->_debug("C: Search base dn: [{$base_dn}] scope [{$scope}] with filter [{$filter}]"); $function = self::scope_to_function($scope, $ns_function); if (!$count_only && ($sort = $this->find_vlv($base_dn, $filter, $scope, $props['sort']))) { // when using VLV, we get the total count by... // ...either reading numSubOrdinates attribute if (($sub_filter = $this->config_get('numsub_filter')) && ($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, array('numSubOrdinates'), 0, 0, 0))) { $counts = ldap_get_entries($this->conn, $result_count); for ($vlv_count = $j = 0; $j < $counts['count']; $j++) { $vlv_count += $counts[$j]['numsubordinates'][0]; } $this->_debug("D: total numsubordinates = " . $vlv_count); } else { if (!function_exists('ldap_parse_virtuallist_control')) { // @FIXME: this search will ignore $props['search'] $vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $props, true); } } $this->vlv_active = $this->_vlv_set_controls($sort, $this->list_page, $this->page_size, $this->_vlv_search($sort, $props['search'])); } else { $this->vlv_active = false; } $sizelimit = (int) $this->config['sizelimit']; $timelimit = (int) $this->config['timelimit']; $phplimit = (int) @ini_get('max_execution_time'); // set LDAP time limit to be (one second) less than PHP time limit // otherwise we have no chance to log the error below if ($phplimit && $timelimit >= $phplimit) { $timelimit = $phplimit - 1; } $this->_debug("Using function {$function} on scope {$scope} (\$ns_function is {$ns_function})"); if ($this->vlv_active) { if (!empty($this->additional_filter)) { $filter = "(&" . $filter . $this->additional_filter . ")"; $this->_debug("C: (With VLV) Setting a filter (with additional filter) of " . $filter); } else { $this->_debug("C: (With VLV) Setting a filter (without additional filter) of " . $filter); } } else { if (!empty($this->additional_filter)) { $filter = "(&" . $filter . $this->additional_filter . ")"; } $this->_debug("C: (Without VLV) Setting a filter of " . $filter); } $this->_debug("Executing search with return attributes: " . var_export($attrs, true)); $ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit); if (!$ldap_result) { $this->_warning("LDAP: {$function} failed for dn={$base_dn}. " . ldap_error($this->conn)); return false; } // when running on a patched PHP we can use the extended functions // to retrieve the total count from the LDAP search result if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) { ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult); $this->_debug("S: VLV result: last_offset={$last_offset}; content_count={$vlv_count}"); } else { $this->_debug("S: " . ($errmsg ? $errmsg : ldap_error($this->conn))); } } else { $this->_debug("S: " . ldap_count_entries($this->conn, $ldap_result) . " record(s) found"); } $result = new Net_LDAP3_Result($this->conn, $base_dn, $filter, $scope, $ldap_result); if (isset($last_offset)) { $result->set('offset', $last_offset); } if (isset($vlv_count)) { $result->set('count', $vlv_count); } $result->set('vlv', $this->vlv_active); return $count_only ? $result->count() : $result; }
/** * Execute the LDAP search based on the stored credentials */ private function _exec_search($count = false) { if ($this->ready) { $filter = $this->filter ? $this->filter : '(objectclass=*)'; $function = $this->_scope2func($this->prop['scope'], $ns_function); $this->_debug("C: Search [{$filter}][dn: {$this->base_dn}]"); // when using VLV, we get the total count by... if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) { // ...either reading numSubOrdinates attribute if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) { $counts = ldap_get_entries($this->conn, $result_count); for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++) { $this->vlv_count += $counts[$j]['numsubordinates'][0]; } $this->_debug("D: total numsubordinates = " . $this->vlv_count); } else { if (!function_exists('ldap_parse_virtuallist_control')) { // ...or by fetching all records dn and count them $this->vlv_count = $this->_exec_search(true); } } $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size); } // only fetch dn for count (should keep the payload low) $attrs = $count ? array('dn') : array_values($this->fieldmap); if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter, $attrs, 0, (int) $this->prop['sizelimit'], (int) $this->prop['timelimit'])) { // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { if (ldap_parse_result($this->conn, $this->ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) { ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $this->vlv_count, $vresult); $this->_debug("S: VLV result: last_offset={$last_offset}; content_count={$this->vlv_count}"); } else { $this->_debug("S: " . ($errmsg ? $errmsg : ldap_error($this->conn))); } } $entries_count = ldap_count_entries($this->conn, $this->ldap_result); $this->_debug("S: {$entries_count} record(s)"); return $count ? $entries_count : true; } else { $this->_debug("S: " . ldap_error($this->conn)); } } return false; }
/** * Execute the LDAP search based on the stored credentials * * @param string $base_dn The base DN to query * @param string $filter The LDAP filter for search * @param string $scope The LDAP scope (list|sub|base) * @param array $attrs List of entry attributes to read * @param array $prop Hash array with query configuration properties: * - sort: array of sort attributes (has to be in sync with the VLV index) * - search: search string used for VLV controls * @param boolean $count_only Set to true if only entry count is requested * * @return mixed rcube_ldap_result object or number of entries (if count_only=true) or false on error */ public function search($base_dn, $filter = '', $scope = 'sub', $attrs = array('dn'), $prop = array(), $count_only = false) { if (!$this->conn) { return false; } if (empty($filter)) { $filter = '(objectclass=*)'; } $this->_debug("C: Search {$base_dn} for {$filter}"); $function = self::scope2func($scope, $ns_function); // find available VLV index for this query if (!$count_only && ($vlv_sort = $this->_find_vlv($base_dn, $filter, $scope, $prop['sort']))) { // when using VLV, we get the total count by... // ...either reading numSubOrdinates attribute if (($sub_filter = $this->config['numsub_filter']) && ($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, array('numSubOrdinates'), 0, 0, 0))) { $counts = ldap_get_entries($this->conn, $result_count); for ($vlv_count = $j = 0; $j < $counts['count']; $j++) { $vlv_count += $counts[$j]['numsubordinates'][0]; } $this->_debug("D: total numsubordinates = " . $vlv_count); } else { if (!function_exists('ldap_parse_virtuallist_control')) { $vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $prop, true); } } $this->vlv_active = $this->_vlv_set_controls($vlv_sort, $this->list_page, $this->page_size, $prop['search']); } else { $this->vlv_active = false; } // only fetch dn for count (should keep the payload low) if ($ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, (int) $this->config['sizelimit'], (int) $this->config['timelimit'])) { // when running on a patched PHP we can use the extended functions // to retrieve the total count from the LDAP search result if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) { ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult); $this->_debug("S: VLV result: last_offset={$last_offset}; content_count={$vlv_count}"); } else { $this->_debug("S: " . ($errmsg ? $errmsg : ldap_error($this->conn))); } } else { if ($this->debug) { $this->_debug("S: " . ldap_count_entries($this->conn, $ldap_result) . " record(s) found"); } } $this->result = new rcube_ldap_result($this->conn, $ldap_result, $base_dn, $filter, $vlv_count); return $count_only ? $this->result->count() : $this->result; } else { $this->_debug("S: " . ldap_error($this->conn)); } return false; }