diff options
author | Aleksander Machniak <machniak@kolabsys.com> | 2015-02-26 11:17:40 (GMT) |
---|---|---|
committer | Aleksander Machniak <machniak@kolabsys.com> | 2015-02-26 11:17:40 (GMT) |
commit | b417fb2ef03ce75162b79170e21a05117cc6cf92 (patch) | |
tree | 0d0e1003a4298c2c140853ef1638fd0c6afce4ad | |
parent | e951e4657a7fa527166d6fc814ffb3484576217e (diff) | |
download | kolab-wap-b417fb2ef03ce75162b79170e21a05117cc6cf92.tar.gz |
Update built-in Net/LDAP3 package
-rw-r--r-- | lib/ext/Net/LDAP3.php | 353 | ||||
-rw-r--r-- | lib/ext/Net/LDAP3/Result.php | 15 |
2 files changed, 290 insertions, 78 deletions
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 | |