@ -38,7 +38,7 @@ class rcube_ldap extends rcube_addressbook
/** private properties */
/** private properties */
protected $conn;
protected $conn;
protected $prop = array();
protected $prop = array();
protected $fieldmap = array();
protected $fieldmap = array('_objclass' => 'objectclass' );
protected $sub_filter;
protected $sub_filter;
protected $filter = '';
protected $filter = '';
protected $result = null;
protected $result = null;
@ -82,6 +82,12 @@ class rcube_ldap extends rcube_addressbook
$this->prop['groups']['name_attr'] = 'cn';
$this->prop['groups']['name_attr'] = 'cn';
if (empty($this->prop['groups']['scope']))
if (empty($this->prop['groups']['scope']))
$this->prop['groups']['scope'] = 'sub';
$this->prop['groups']['scope'] = 'sub';
// add group name attrib to fieldmap in order to have it fetched
$this->fieldmap['_groupname'] = $this->prop['groups']['name_attr'];
}
else if (is_array($p['group_filters']) & & count($p['group_filters'])) {
$this->groups = true;
}
}
// fieldmap property is given
// fieldmap property is given
@ -549,13 +555,15 @@ class rcube_ldap extends rcube_addressbook
}
}
else
else
{
{
$prop = $this->group_id ? $this->group_data : $this->prop;
// add general filter to query
// add general filter to query
if (!empty($this->prop['filter']) & & empty($this->filter))
if (!empty($prop['filter']) & & empty($this->filter))
$this->set_search_set($this-> prop['filter']);
$this->set_search_set($prop['filter']);
// exec LDAP search if no result resource is stored
// exec LDAP search if no result resource is stored
if ($this->conn & & !$this->ldap_result)
if ($this->conn & & !$this->ldap_result)
$this->_exec_search();
$this->_exec_search($prop );
// count contacts for this user
// count contacts for this user
$this->result = $this->count();
$this->result = $this->count();
@ -564,7 +572,7 @@ class rcube_ldap extends rcube_addressbook
if ($this->ldap_result & & $this->result->count > 0)
if ($this->ldap_result & & $this->result->count > 0)
{
{
// sorting still on the ldap server
// sorting still on the ldap server
if ($this->sort_col & & $this-> prop['scope'] !== 'base' & & !$this->vlv_active)
if ($this->sort_col & & $prop['scope'] !== 'base' & & !$this->vlv_active)
ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
// get all entries from the ldap server
// get all entries from the ldap server
@ -599,6 +607,7 @@ class rcube_ldap extends rcube_addressbook
// fetch group object
// fetch group object
if (empty($entries)) {
if (empty($entries)) {
$this->_debug("C: Read Group [dn: $dn]");
$result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL'));
$result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL'));
if ($result === false)
if ($result === false)
{
{
@ -657,21 +666,6 @@ class rcube_ldap extends rcube_addressbook
if (empty($entry[$attr]))
if (empty($entry[$attr]))
return $group_members;
return $group_members;
// add group record to cache if it isn't yet there
$group_id = self::dn_encode($dn);
$group_cache = $this->cache->get('groups');
if (!$group_cache[$group_id]) {
$name_attr = $this->prop['groups']['name_attr'];
$group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr];
$group_cache[$group_id]['ID'] = $group_id;
$group_cache[$group_id]['dn'] = $dn;
$group_cache[$group_id]['name'] = $group_name;
$group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry[$i]['objectclass']);
$this->cache->set('groups', $group_cache);
}
// read these attributes for all members
// read these attributes for all members
$attrib = $count ? array('dn') : array_values($this->fieldmap);
$attrib = $count ? array('dn') : array_values($this->fieldmap);
$attrib[] = 'objectClass';
$attrib[] = 'objectClass';
@ -726,26 +720,15 @@ class rcube_ldap extends rcube_addressbook
// add search filter if any
// add search filter if any
$filter = $this->filter ? '(& (' . $m[3] . ')(' . $this->filter . '))' : $m[3];
$filter = $this->filter ? '(& (' . $m[3] . ')(' . $this->filter . '))' : $m[3];
$func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list');
$attrs = $count ? array('dn') : array_values($this->fieldmap);
if ($result = $this->ldap_search($m[1], $filter, $m[2], $attrs, $this->group_data)) {
$attrib = $count ? array('dn') : array_values($this->fieldmap);
$entries = @ldap_get_entries($this->conn, $result);
if ($result = @$func($this->conn, $m[1], $filter,
for ($j = 0; $j < $entries['count']; $j++) {
$attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
) {
$group_members = array_merge($group_members, $nested_group_members);
$this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]);
else
}
$group_members[] = $entries[$j];
else {
}
$this->_debug("S: ".ldap_error($this->conn));
return $group_members;
}
$entries = @ldap_get_entries($this->conn, $result);
for ($j = 0; $j < $entries['count']; $j++)
{
if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
$group_members = array_merge($group_members, $nested_group_members);
else
$group_members[] = $entries[$j];
}
}
}
}
@ -910,7 +893,7 @@ class rcube_ldap extends rcube_addressbook
// set filter string and execute search
// set filter string and execute search
$this->set_search_set($filter);
$this->set_search_set($filter);
$this->_exec_search();
$this->_exec_search($this->prop );
if ($select)
if ($select)
$this->list_records();
$this->list_records();
@ -936,13 +919,15 @@ class rcube_ldap extends rcube_addressbook
$count = count($this->list_group_members($this->group_data['dn'], true));
$count = count($this->list_group_members($this->group_data['dn'], true));
}
}
else if ($this->conn) {
else if ($this->conn) {
$prop = $this->group_id ? $this->group_data : $this->prop;
// We have a connection but no result set, attempt to get one.
// We have a connection but no result set, attempt to get one.
if (empty($this->filter)) {
if (empty($this->filter)) {
// The filter is not set, set it.
// The filter is not set, set it.
$this->filter = $this->prop['filter'];
$this->filter = $this->prop['filter'];
}
}
$count = (int) $this->_exec_search(true);
$count = (int) $this->_exec_search($prop, true);
}
}
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
@ -1434,57 +1419,35 @@ class rcube_ldap extends rcube_addressbook
/**
/**
* Execute the LDAP search based on the stored credentials
* Execute the LDAP search based on the stored credentials
*/
*/
private function _exec_search($count = false)
private function _exec_search($prop, $ count = false)
{
{
if ($this->ready)
if ($this->ready)
{
{
$filter = $this->filter ? $this->filter : '(objectclass=*)';
$function = $this->_scope2func($prop['scope'], $ns_function);
$function = $this->_scope2func($this->prop['scope'], $ns_function);
$base_dn = $this->group_id & & $prop['base_dn'] ? $prop['base_dn'] : $this->base_dn;
$this->_debug("C: Search [$filter][dn: $this->base_dn]");
// when using VLV, we get the total count by...
// when using VLV, we get the total count by...
if (!$count & & $function != 'ldap_read' & & $this-> prop['vlv'] & & !$this->group_id ) {
if (!$count & & $function != 'ldap_read' & & $prop['vlv']) {
// ...either reading numSubOrdinates attribute
// ...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))) {
if ($prop['numsub_filter'] & & ($result_count = @$ns_function($this->conn, $base_dn, $prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) {
$counts = ldap_get_entries($this->conn, $result_count);
$counts = ldap_get_entries($this->conn, $result_count);
for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++)
for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++)
$this->vlv_count += $counts[$j]['numsubordinates'][0];
$this->vlv_count += $counts[$j]['numsubordinates'][0];
$this->_debug("D: total numsubordinates = " . $this->vlv_count);
$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
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_count = $this->_exec_search($prop, 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)
$filter = $this->filter ? $this->filter : '(objectclass=*)';
$attrs = $count ? array('dn') : array_values($this->fieldmap);
$attrs = $count ? array('dn') : array_values($this->fieldmap); // only fetch dn for count (should keep the payload low)
if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter,
if ($this->ldap_result = $this->ldap_search($base_dn, $filter, $prop['scope'], $attrs, $prop)) {
$attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
if ($count) {
) {
return ldap_count_entries($this->conn, $this->ldap_result);
// 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')) {
else {
if (ldap_parse_result($this->conn, $this->ldap_result,
return true;
$errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
& & $serverctrls // can be null e.g. in case of adm. limit error
) {
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));
}
}
}
}
@ -1540,20 +1503,19 @@ class rcube_ldap extends rcube_addressbook
private function _ldap2result($rec)
private function _ldap2result($rec)
{
{
$out = array('_type' => 'person');
$out = array('_type' => 'person');
$fieldmap = $this->fieldmap;
if ($rec['dn'])
if ($rec['dn'])
$out[$this->primary_key] = self::dn_encode($rec['dn']);
$out[$this->primary_key] = self::dn_encode($rec['dn']);
// determine record type
// determine record type
if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), (array)$rec['objectclass'])) {
if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), array_map('strtolower', (array)$rec['objectclass']) )) {
$out['_type'] = 'group';
$out['_type'] = 'group';
$out['readonly'] = true;
$out['readonly'] = true;
if ($this->fieldmap['groupname']) {
$fieldmap['name'] = $fieldmap['_groupname'];
$this->fieldmap['name'] = $this->fieldmap['groupname'];
}
}
}
foreach ($this-> fieldmap as $rf => $lf)
foreach ($fieldmap as $rf => $lf)
{
{
for ($i=0; $i < $rec[$lf]['count']; $i++) {
for ($i=0; $i < $rec[$lf]['count']; $i++) {
if (!($value = $rec[$lf][$i]))
if (!($value = $rec[$lf][$i]))
@ -1718,20 +1680,14 @@ class rcube_ldap extends rcube_addressbook
/**
/**
* Setter for the current group
* Setter for the current group
* (empty, has to be re-implemented by extending class)
*/
*/
function set_group($group_id)
function set_group($group_id)
{
{
if ($group_id)
if ($group_id) {
{
if (($group_cache = $this->cache->get('groups')) === null)
$group_cache = $this->_fetch_groups();
$this->group_id = $group_id;
$this->group_id = $group_id;
$this->group_data = $group_cache[$group_id] ;
$this->group_data = $this->get_group_entry($group_id);
}
}
else
else {
{
$this->group_id = 0;
$this->group_id = 0;
$this->group_data = null;
$this->group_data = null;
}
}
@ -1772,6 +1728,23 @@ class rcube_ldap extends rcube_addressbook
*/
*/
private function _fetch_groups($vlv_page = 0)
private function _fetch_groups($vlv_page = 0)
{
{
// special case: list groups from 'group_filters' config
if (!empty($this->prop['group_filters'])) {
$groups = array();
// list regular groups configuration as special filter
if (!empty($this->prop['groups']['filter'])) {
$id = '__groups__';
$groups[$id] = array('ID' => $id, 'name' => rcube_label('groups')) + $this->prop['groups'];
}
foreach ($this->prop['group_filters'] as $id => $prop) {
$groups[$id] = $prop + array('ID' => $id, 'name' => ucfirst($id));
}
return $groups;
}
$base_dn = $this->groups_base_dn;
$base_dn = $this->groups_base_dn;
$filter = $this->prop['groups']['filter'];
$filter = $this->prop['groups']['filter'];
$name_attr = $this->prop['groups']['name_attr'];
$name_attr = $this->prop['groups']['name_attr'];
@ -1843,6 +1816,40 @@ class rcube_ldap extends rcube_addressbook
return $groups;
return $groups;
}
}
/**
* Fetch a group entry from LDAP and save in local cache
*/
private function get_group_entry($group_id)
{
if (($group_cache = $this->cache->get('groups')) === null)
$group_cache = $this->_fetch_groups();
// add group record to cache if it isn't yet there
if (!isset($group_cache[$group_id])) {
$name_attr = $this->prop['groups']['name_attr'];
$dn = self::dn_decode($group_id);
$this->_debug("C: Read Group [dn: $dn]");
if ($result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL',$name_attr))) {
$list = ldap_get_entries($this->conn, $result);
$entry = $list[0];
$group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr];
$group_cache[$group_id]['ID'] = $group_id;
$group_cache[$group_id]['dn'] = $dn;
$group_cache[$group_id]['name'] = $group_name;
$group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']);
}
else {
$this->_debug("S: ".ldap_error($this->conn));
$group_cache[$group_id] = false;
}
$this->cache->set('groups', $group_cache);
}
return $group_cache[$group_id];
}
/**
/**
* Get group properties such as name and email address(es)
* Get group properties such as name and email address(es)
*
*
@ -2245,6 +2252,59 @@ class rcube_ldap extends rcube_addressbook
return base64_decode($str);
return base64_decode($str);
}
}
/**
* Wrapper for ldap_search or ldap_list depending on the given scope.
* It optionally uses VLV index if configured in $prop
*
* @param string Base DN to read from
* @param string Query filter to use
* @param string Listing scope (sub|list|base)
* @param array List of entry attributes to read
* @param array Hash array with query configuration properties:
* - vlv: true if VLV index should be used
* - sort: array of sort attributes (has to be in sync with the VLV index)
*/
protected function ldap_search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $prop = array())
{
if ($this->ready) {
$this->_debug("C: LDAP Search [$filter][dn: $base_dn]");
// set VLV controls if requested
if ($prop['vlv'] & & $scope != 'base')
$this->vlv_active = $this->_vlv_set_controls($prop, $this->list_page, $this->page_size);
$function = $this->_scope2func($scope);
if ($ldap_result = $function($this->conn, $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)
& & $serverctrls // can be null e.g. in case of adm. limit error
) {
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, $ldap_result);
$this->_debug("S: $entries_count record(s)");
return $ldap_result;
}
else {
$this->_debug("S: ".ldap_error($this->conn));
}
}
return false;
}
/**
/**
* Wrapper for ldap_add()
* Wrapper for ldap_add()
*/
*/