lib/ext
Aleksander Machniak
machniak at kolabsys.com
Thu Feb 26 12:18:05 CET 2015
lib/ext/Net/LDAP3.php | 353 +++++++++++++++++++++++++++++++++----------
lib/ext/Net/LDAP3/Result.php | 15 +
2 files changed, 290 insertions(+), 78 deletions(-)
New commits:
commit b417fb2ef03ce75162b79170e21a05117cc6cf92
Author: Aleksander Machniak <machniak at kolabsys.com>
Date: Thu Feb 26 12:17:40 2015 +0100
Update built-in Net/LDAP3 package
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 7e463f0..37a0ea3 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -8,9 +8,18 @@
| Copyright (C) 2006-2014, The Roundcube Dev Team |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for plugins. |
- | See the README file for a full license statement. |
+ | This program is free software: you can redistribute it and/or modify |
+ | it under the terms of the GNU 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 General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| PURPOSE: |
| Provide advanced functionality for accessing LDAP directories |
@@ -689,15 +698,16 @@ class Net_LDAP3
'entryLevelRights' => array(),
);
- $output = array();
$entry_dn = $this->entry_dn($subject);
if (!$entry_dn) {
$entry_dn = $this->config_get($subject . "_base_dn");
}
+
if (!$entry_dn) {
$entry_dn = $this->config_get("base_dn");
}
+
if (!$entry_dn) {
$entry_dn = $this->config_get("root_dn");
}
@@ -717,34 +727,68 @@ class Net_LDAP3
return null;
}
- $command = array(
- $moz_ldapsearch,
- '-x',
- '-h',
- $this->_ldap_server,
- '-p',
- $this->_ldap_port,
- '-b',
- escapeshellarg($entry_dn),
- '-D',
- escapeshellarg($this->_current_bind_dn),
- '-w',
- escapeshellarg($this->_current_bind_pw),
- '-J',
- escapeshellarg(implode(':', array(
- $effective_rights_control_oid, // OID
- 'true', // Criticality
- 'dn:' . $this->_current_bind_dn // User DN
- ))),
- '-s',
- 'base',
- '"(objectclass=*)"',
- '"*"',
- );
+ $output = array();
+ $command = Array(
+ $moz_ldapsearch,
+ '-x',
+ '-h',
+ $this->_current_host,
+ '-p',
+ $this->config_get('port', 389),
+ '-b',
+ escapeshellarg($entry_dn),
+ '-s',
+ 'base',
+ '-D',
+ escapeshellarg($this->_current_bind_dn),
+ '-w',
+ escapeshellarg($this->_current_bind_pw)
+ );
+
+ if ($this->vendor_name() == "Oracle Corporation") {
+ // For Oracle DSEE
+ $command[] = "-J";
+ $command[] = escapeshellarg(
+ implode(
+ ':',
+ Array(
+ $effective_rights_control_oid, // OID
+ 'true' // Criticality
+ )
+ )
+ );
+ $command[] = "-c";
+ $command[] = escapeshellarg(
+ 'dn:' . $this->_current_bind_dn
+ );
+
+ } else {
+ // For 389 DS:
+ $command[] = "-J";
+ $command[] = escapeshellarg(
+ implode(
+ ':',
+ Array(
+ $effective_rights_control_oid, // OID
+ 'true', // Criticality
+ 'dn:' . $this->_current_bind_dn // User DN
+ )
+ )
+ );
+ }
+
+ // For both
+ $command[] = '"(objectclass=*)"';
+ $command[] = '"*"';
+
+ if ($this->vendor_name() == "Oracle Corporation") {
+ // Oracle DSEE
+ $command[] = 'aclRights';
+ }
// remove password from debug log
$command_debug = $command;
- $command_debug[11] = '*';
+ $command_debug[13] = '*';
$command = implode(' ', $command);
$command_debug = implode(' ', $command_debug);
@@ -771,24 +815,46 @@ class Net_LDAP3
}
}
- foreach ($lines as $line) {
- $line_components = explode(':', $line);
- $attribute_name = array_shift($line_components);
- $attribute_value = trim(implode(':', $line_components));
-
- switch ($attribute_name) {
- case "attributeLevelRights":
- $attributes[$attribute_name] = $this->parse_attribute_level_rights($attribute_value);
- break;
- case "dn":
- $attributes[$attribute_name] = $attribute_value;
- break;
- case "entryLevelRights":
- $attributes[$attribute_name] = $this->parse_entry_level_rights($attribute_value);
- break;
+ if ($this->vendor_name() == "Oracle Corporation") {
+ // Example for attribute level rights:
+ // aclRights;attributeLevel;$attr:$right:$bool,$right:$bool
+ // Example for entry level rights:
+ // aclRights;entryLevel: add:1,delete:1,read:1,write:1,proxy:1
+ foreach ($lines as $line) {
+ $line_components = explode(':', $line);
+ $attribute_name = explode(';', array_shift($line_components));
+
+ switch ($attribute_name[0]) {
+ case "aclRights":
+ $this->parse_aclrights($attributes, $line);
+ break;
+ case "dn":
+ $attributes[$attribute_name[0]] = trim(implode(';', $line_components));
+ break;
+ default:
+ break;
+ }
+ }
- default:
- break;
+ } else {
+ foreach ($lines as $line) {
+ $line_components = explode(':', $line);
+ $attribute_name = array_shift($line_components);
+ $attribute_value = trim(implode(':', $line_components));
+
+ switch ($attribute_name) {
+ case "attributeLevelRights":
+ $attributes[$attribute_name] = $this->parse_attribute_level_rights($attribute_value);
+ break;
+ case "dn":
+ $attributes[$attribute_name] = $attribute_value;
+ break;
+ case "entryLevelRights":
+ $attributes[$attribute_name] = $this->parse_entry_level_rights($attribute_value);
+ break;
+ default:
+ break;
+ }
}
}
@@ -1192,8 +1258,8 @@ class Net_LDAP3
// This is me cheating. Remove this special attribute.
if (array_key_exists('ou', $old_attrs) || array_key_exists('ou', $new_attrs)) {
- $old_ou = $old_attrs['ou'];
- $new_ou = $new_attrs['ou'];
+ $old_ou = is_array($old_attrs['ou']) ? array_shift($old_attrs['ou']) : $old_attrs['ou'];
+ $new_ou = is_array($new_attrs['ou']) ? array_shift($new_attrs['ou']) : $new_attrs['ou'];
unset($old_attrs['ou']);
unset($new_attrs['ou']);
}
@@ -1495,12 +1561,18 @@ class Net_LDAP3
return false;
}
- $this->_debug("C: Search base dn: [$base_dn] scope [$scope] with filter [$filter]");
-
// 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...
@@ -1516,6 +1588,7 @@ class Net_LDAP3
}
// ...or by fetching all records dn and count them
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);
}
@@ -1526,9 +1599,15 @@ class Net_LDAP3
$this->vlv_active = false;
}
- $function = self::scope_to_function($scope, $ns_function);
$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)");
@@ -1553,7 +1632,7 @@ class Net_LDAP3
$ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit);
if (!$ldap_result) {
- $this->_debug("$function failed for dn=$base_dn: ".ldap_error($this->conn));
+ $this->_warning("$function failed for dn=$base_dn: ".ldap_error($this->conn));
return false;
}
@@ -1568,14 +1647,20 @@ class Net_LDAP3
$this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
}
}
- else if ($this->debug) {
+ 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);
- $result->set('offset', $last_offset);
- $result->set('count', $vlv_count);
- $result->set('vlv', true);
+
+ 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;
}
@@ -1636,6 +1721,8 @@ class Net_LDAP3
$filter = '';
foreach ((array) $search['params'] as $field => $param) {
+ $value = (array) $param['value'];
+
switch ((string)$param['type']) {
case 'prefix':
$prefix = '';
@@ -1654,6 +1741,13 @@ class Net_LDAP3
case '<=':
$prefix = '';
$suffix = '';
+
+ // this is a common query to find entry by DN, make sure
+ // it is a unified DN so special characters are handled correctly
+ if ($field == 'entrydn') {
+ $value = array_map(array('Net_LDAP3', 'unified_dn'), $value);
+ }
+
break;
case 'exists':
@@ -1671,16 +1765,20 @@ class Net_LDAP3
$operator = $param['type'] && in_array($param['type'], $operators) ? $param['type'] : '=';
- if (is_array($param['value'])) {
+ if (count($value) < 2) {
+ $value = array_pop($value);
+ }
+
+ if (is_array($value)) {
$val_filter = array();
- foreach ($param['value'] as $val) {
- $value = self::quote_string($val);
- $val_filter[] = "(" . $field . $operator . $prefix . $value . $suffix . ")";
+ foreach ($value as $val) {
+ $val = self::quote_string($val);
+ $val_filter[] = "(" . $field . $operator . $prefix . $val . $suffix . ")";
}
$filter .= "(|" . implode($val_filter, '') . ")";
}
else {
- $value = self::quote_string($param['value']);
+ $value = self::quote_string($value);
$filter .= "(" . $field . $operator . $prefix . $value . $suffix . ")";
}
}
@@ -1861,25 +1959,28 @@ class Net_LDAP3
// Not passing any sort attributes means you don't care
if (!empty($sort_attrs)) {
- $sort_attrs = (array) $sort_attrs;
+ $sort_attrs = array_map('strtolower', (array) $sort_attrs);
+
foreach ($vlv_index[$base_dn]['sort'] as $sss_config) {
+ $sss_config = array_map('strtolower', $sss_config);
if (count(array_intersect($sort_attrs, $sss_config)) == count($sort_attrs)) {
+ $this->_debug("Sorting matches");
+
return $sort_attrs;
}
}
- $this->_error("The requested sorting does not match any server-side sorting configuration");
-
- return false;
+ $this->_debug("Sorting does not match");
}
else {
- return $vlv_index[$base_dn]['sort'][0];
+ $sort = array_filter((array) $vlv_index[$base_dn]['sort'][0]);
+ $this->_debug("Sorting unimportant");
+
+ return $sort;
}
}
else {
- $this->_debug("Scope does not match. VLV: " . var_export($vlv_index[$base_dn]['scope'], true)
- . " while looking for " . var_export($scope, true));
- return false;
+ $this->_debug("Scope does not match");
}
}
else {
@@ -1897,14 +1998,47 @@ class Net_LDAP3
*/
protected function find_vlv_indexes_and_searches()
{
+ // Use of Virtual List View control has been specifically disabled.
if ($this->config['vlv'] === false) {
return false;
}
+ // Virtual List View control has been configured in kolab.conf, for example;
+ //
+ // [ldap]
+ // vlv = [
+ // {
+ // 'ou=People,dc=example,dc=org': {
+ // 'scope': 'sub',
+ // 'filter': '(objectclass=inetorgperson)',
+ // 'sort' : [
+ // [
+ // 'displayname',
+ // 'sn',
+ // 'givenname',
+ // 'cn'
+ // ]
+ // ]
+ // }
+ // },
+ // {
+ // 'ou=Groups,dc=example,dc=org': {
+ // 'scope': 'sub',
+ // 'filter': '(objectclass=groupofuniquenames)',
+ // 'sort' : [
+ // [
+ // 'cn'
+ // ]
+ // ]
+ // }
+ // },
+ // ]
+ //
if (is_array($this->config['vlv'])) {
return $this->config['vlv'];
}
+ // We have done this dance before.
if ($this->_vlv_indexes_and_searches !== null) {
return $this->_vlv_indexes_and_searches;
}
@@ -1962,7 +2096,7 @@ class Net_LDAP3
$_vlv_sort = array();
foreach ($vlv_indexes as $vlv_index_dn => $vlv_index_attrs) {
- $_vlv_sort[] = explode(' ', $vlv_index_attrs['vlvsort']);
+ $_vlv_sort[] = explode(' ', trim($vlv_index_attrs['vlvsort']));
}
$this->_vlv_indexes_and_searches[] = array(
@@ -2247,6 +2381,45 @@ class Net_LDAP3
return true;
}
+ private function parse_aclrights(&$attributes, $attribute_value) {
+ $components = explode(':', $attribute_value);
+ $_acl_target = array_shift($components);
+ $_acl_value = trim(implode(':', $components));
+
+ $_acl_components = explode(';', $_acl_target);
+
+ switch ($_acl_components[1]) {
+ case "entryLevel":
+ $attributes['entryLevelRights'] = Array();
+ $_acl_value = explode(',', $_acl_value);
+
+ foreach ($_acl_value as $right) {
+ list($method, $bool) = explode(':', $right);
+ if ($bool == "1" && !in_array($method, $attributes['entryLevelRights'])) {
+ $attributes['entryLevelRights'][] = $method;
+ }
+ }
+
+ break;
+
+ case "attributeLevel":
+ $attributes['attributeLevelRights'][$_acl_components[2]] = Array();
+ $_acl_value = explode(',', $_acl_value);
+
+ foreach ($_acl_value as $right) {
+ list($method, $bool) = explode(':', $right);
+ if ($bool == "1" && !in_array($method, $attributes['attributeLevelRights'][$_acl_components[2]])) {
+ $attributes['attributeLevelRights'][$_acl_components[2]][] = $method;
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
private function parse_attribute_level_rights($attribute_value)
{
$attribute_value = str_replace(", ", ",", $attribute_value);
@@ -2309,6 +2482,33 @@ class Net_LDAP3
return $control;
}
+ private function vendor_name()
+ {
+ if (!empty($this->vendor_name)) {
+ return $this->vendor_name;
+ }
+
+ $this->_info("Obtaining LDAP server vendor name");
+
+ if ($result = $this->search('', '(objectclass=*)', 'base', array('vendorname'))) {
+ $result = $result->entries(true);
+ $name = $result['']['vendorname'];
+ }
+ else {
+ $name = false;
+ }
+
+ if ($name !== false) {
+ $this->_info("Vendor name is $name");
+ } else {
+ $this->_info("No vendor name!");
+ }
+
+ $this->vendor = $name;
+
+ return $name;
+ }
+
protected function _alert()
{
$this->__log(LOG_ALERT, func_get_args());
@@ -2606,6 +2806,11 @@ class Net_LDAP3
/**
* Return the search string value to be used in VLV controls
+ *
+ * @param array $sort List of attributes in vlv index
+ * @param array|string $search Search string or attribute => value hash
+ *
+ * @return string Search string
*/
private function _vlv_search($sort, $search)
{
@@ -2618,15 +2823,13 @@ class Net_LDAP3
return;
}
- $search_suffix = $this->_fuzzy_search_suffix();
-
- foreach ($search as $attr => $value) {
- if (!in_array(strtolower($attr), $sort)) {
+ foreach ((array) $search as $attr => $value) {
+ if ($attr && !in_array(strtolower($attr), $sort)) {
$this->_debug("Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
return;
}
else {
- return $value . $search_suffix;
+ return $value . $this->_fuzzy_search_suffix();
}
}
}
diff --git a/lib/ext/Net/LDAP3/Result.php b/lib/ext/Net/LDAP3/Result.php
index 0759df0..b89c9fd 100644
--- a/lib/ext/Net/LDAP3/Result.php
+++ b/lib/ext/Net/LDAP3/Result.php
@@ -9,9 +9,18 @@
| Copyright (C) 2006-2014, The Roundcube Dev Team |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for plugins. |
- | See the README file for a full license statement. |
+ | This program is free software: you can redistribute it and/or modify |
+ | it under the terms of the GNU 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 General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| PURPOSE: |
| Provide advanced functionality for accessing LDAP directories |
More information about the commits
mailing list