Page MenuHomePhorge

LDAP.php
No OneTemporary

Authored By
Unknown
Size
47 KB
Referenced Files
None
Subscribers
None

LDAP.php

<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| 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/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Kolab LDAP handling abstraction class.
*/
class LDAP
{
public $_name = "LDAP";
private $conn;
private $bind_dn;
private $bind_pw;
private $attribute_level_rights_map = array(
"r" => "read",
"s" => "search",
"w" => "write",
"o" => "delete",
"c" => "compare",
"W" => "write",
"O" => "delete"
);
private $entry_level_rights_map = array(
"a" => "add",
"d" => "delete",
"n" => "modrdn",
"v" => "read"
);
// This is the default and should actually be set through Conf.
private $_ldap_uri = 'ldap://localhost:389/';
private $conf;
/**
* Class constructor
*/
public function __construct($domain = null)
{
$this->conf = Conf::get_instance();
// See if we are to connect to any domain explicitly defined.
if (!isset($domain) || empty($domain)) {
// If not, attempt to get the domain from the session.
if (isset($_SESSION['user'])) {
try {
$domain = $_SESSION['user']->get_domain();
} catch (Exception $e) {
// TODO: Debug logging
error_log("Warning, user not authenticated yet");
}
}
}
// Continue and default to the primary domain.
$this->domain = $domain ? $domain : $this->conf->get('primary_domain');
$this->_ldap_uri = $this->conf->get('ldap_uri');
$this->_ldap_server = parse_url($this->_ldap_uri, PHP_URL_HOST);
$this->_ldap_port = parse_url($this->_ldap_uri, PHP_URL_PORT);
$this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME);
// Catch cases in which the ldap server port has not been explicitely defined
if (!$this->_ldap_port) {
if ($this->_ldap_scheme == "ldaps") {
$this->_ldap_port = 636;
}
else {
$this->_ldap_port = 389;
}
}
// We can also use the parse_url() to pass on the bind dn and pw:
//
// $ldap = new LDAP('ldap://uid=svc-kwap,ou=Services,ou=Accounts,dc=kanarip,dc=com:VerySecret@localhost/');
// and the following line uncommented:
//
// echo "<pre>"; print_r(parse_url($ldap_uri)); echo "</pre>";
//
// creates:
//
// array
// (
// [scheme] => ldap
// [host] => localhost
// [user] => uid=svc-kwap,ou=Services,ou=Accounts,dc=kanarip,dc=com
// [pass] => VerySecret
// [path] => /
// )
}
/**********************************************************
*********** Public methods ************
**********************************************************/
public function authenticate($username, $password)
{
error_log("LDAP authentication request for $username");
if (!$this->_connect()) {
return false;
}
// Attempt to explode the username to see if it is in fact a DN,
// such as would be the case for 'cn=Directory Manager' or
// 'uid=admin'.
$is_dn = ldap_explode_dn($username, 1);
if (!$is_dn) {
error_log("Username is not a DN");
list($this->userid, $this->domain) = $this->_qualify_id($username);
$root_dn = $this->domain_root_dn($this->domain);
$user_dn = $this->_get_user_dn($root_dn, '(mail=' . $username . ')');
error_log("Found user DN: $user_dn for user: $username");
}
else {
$user_dn = $username;
$root_dn = "";
}
if (($bind_ok = $this->_bind($user_dn, $password)) == true) {
$this->_unbind();
if (isset($_SESSION['user'])) {
$_SESSION['user']->user_root_dn = $root_dn;
$_SESSION['user']->user_bind_dn = $user_dn;
$_SESSION['user']->user_bind_pw = $password;
error_log("Successfully bound with User DN: " . $_SESSION['user']->user_bind_dn);
}
else {
error_log("Successfully bound with User DN: " . $user_dn . " but not saving it to the session");
}
return true;
}
else {
error_log("LDAP Error: " . $this->_errstr());
return false;
}
}
public function domain_add($domain, $domain_alias = false, $prepopulate = true)
{
// Apply some routines for access control to this function here.
if ($domain_alias) {
return $this->_domain_add_alias($domain, $domain_alias);
}
else {
return $this->_domain_add_new($domain, $prepopulate);
}
}
public function effective_rights($subject_dn)
{
$attributes = array();
$output = array();
$conf = Conf::get_instance();
$command = array(
// TODO: Very 64-bit specific
'/usr/lib64/mozldap/ldapsearch',
'-x',
'-h',
$this->_ldap_server,
'-p',
$this->_ldap_port,
'-b',
$conf->get('base_dn'),
'-D',
'"' . $_SESSION['user']->user_bind_dn . '"',
'-w',
'"' . $_SESSION['user']->user_bind_pw . '"',
'-J',
'"' . implode(
':',
array(
'1.3.6.1.4.1.42.2.27.9.5.2', // OID
'true', // Criticality
'dn:' . $_SESSION['user']->user_bind_dn // User DN
)
) . '"',
'"(entrydn=' . $subject_dn . ')"'
);
exec(implode(' ', $command), $output);
$lines = array();
foreach ($output as $line_num => $line) {
if (substr($line, 0, 1) == " ") {
$lines[count($lines)-1] .= trim($line);
} else {
$lines[] = trim($line);
}
}
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;
}
}
return $attributes;
}
public function get_attribute($subject_dn, $attribute)
{
$result = ldap_read($this->conn, $subject_dn, '(objectclass=*)', (array)($attribute));
console($result);
}
public function get_attributes($subject_dn, $attributes)
{
$result = $this->search($subject_dn, '(objectclass=*)', $attributes);
$result = self::normalize_result($result);
if (!empty($result)) {
$result = array_pop($result);
return $result;
}
return false;
}
public function group_find_by_attribute($attribute)
{
if (empty($attribute) || !is_array($attribute) || count($attribute) > 1) {
return false;
}
if (empty($attribute[key($attribute)])) {
return false;
}
$filter = "(&";
foreach ($attribute as $key => $value) {
$filter .= "(" . $key . "=" . $value . ")";
}
$filter .= ")";
$base_dn = $this->domain_root_dn($this->domain);
$result = self::normalize_result($this->search($base_dn, $filter, array_keys($attribute)));
if (count($result) > 0) {
error_log("Results found: " . implode(', ', array_keys($result)));
return $result;
}
else {
error_log("No result");
return false;
}
}
public function list_domains()
{
$domains = $this->domains_list();
$domains = self::normalize_result($domains);
return $domains;
}
public function list_groups($attributes = array(), $search = array(), $params = array())
{
if (!empty($params['sort_by'])) {
if (!in_array($params['sort_by'], $attributes)) {
$attributes[] = $params['sort_by'];
}
}
$groups = $this->groups_list($attributes, $search);
$groups = self::normalize_result($groups);
if (!empty($params['sort_by'])) {
$this->sort_result_key = $params['sort_by'];
uasort($groups, array($this, 'sort_result'));
if ($params['sort_order'] == 'DESC') {
$groups = array_reverse($groups, true);
}
}
return $groups;
}
public function list_users($attributes = array(), $search = array(), $params = array())
{
if (!empty($params['sort_by'])) {
if (!in_array($params['sort_by'], $attributes)) {
$attributes[] = $params['sort_by'];
}
}
$users = $this->users_list($attributes, $search);
$users = self::normalize_result($users);
if (!empty($params['sort_by'])) {
$this->sort_result_key = $params['sort_by'];
uasort($users, array($this, 'sort_result'));
if ($params['sort_order'] == 'DESC') {
$users = array_reverse($users, true);
}
}
return $users;
}
public function list_roles($attributes = array(), $search = array(), $params = array())
{
if (!empty($params['sort_by'])) {
if (!in_array($params['sort_by'], $attributes)) {
$attributes[] = $params['sort_by'];
}
}
$roles = $this->roles_list($attributes, $search);
$roles = self::normalize_result($roles);
if (!empty($params['sort_by'])) {
$this->sort_result_key = $params['sort_by'];
uasort($roles, array($this, 'sort_result'));
if ($params['sort_order'] == 'DESC') {
$roles = array_reverse($roles, true);
}
}
return $roles;
}
public function modify_entry($subject_dn, $old_attrs, $new_attrs)
{
console($old_attrs);
// TODO: Get $rdn_attr - we have type_id in $new_attrs
$dn_components = ldap_explode_dn($subject_dn, 0);
$rdn_components = explode('=', $dn_components[0]);
$rdn_attr = $rdn_components[0];
console($rdn_attr);
// return;
$mod_array = Array(
"add" => Array(), // For use with ldap_mod_add()
"del" => Array(), // For use with ldap_mod_del()
"replace" => Array(), // For use with ldap_mod_replace()
"rename" => Array(), // For use with ldap_rename()
);
// Compare each attribute value of the old attrs with the corresponding value
// in the new attrs, if any.
foreach ($old_attrs as $attr => $old_attr_value) {
if (array_key_exists($attr, $new_attrs)) {
if (!($new_attrs[$attr] === $old_attr_value)) {
console("Attribute $attr changed from", $old_attr_value, "to", $new_attrs[$attr]);
if ($attr === $rdn_attr) {
$mod_array['rename'][$subject_dn] = $rdn_attr . '=' . $new_attrs[$attr];
} else {
console("Adding to replace: $attr");
$mod_array['replace'][$attr] = (array)($new_attrs[$attr]);
}
} else {
console("Attribute $attr unchanged");
}
} else {
// TODO: Since we're not shipping the entire object back and forth, and only post
// part of the data... we don't know what is actually removed (think modifiedtimestamp, etc.)
console("Group attribute $attr not mentioned in \$new_attrs..., but not explicitly removed... by assumption");
}
}
foreach ($new_attrs as $attr => $value) {
if (array_key_exists($attr, $old_attrs)) {
if (!($old_attrs[$attr] === $value) && !($attr === $rdn_attr)) {
if (!array_key_exists($attr, $mod_array['replace'])) {
console("Adding to replace(2): $attr");
$mod_array['replace'][$attr] = $value;
}
}
} else {
$mod_array['add'][$attr] = $value;
}
}
console($mod_array);
$result = $this->modify_entry_attributes($subject_dn, $mod_array);
if ($result) {
return $mod_array;
}
}
public function modify_entry_attributes($subject_dn, $attributes)
{
$this->_bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
// Opportunities to set false include failed ldap commands.
$result = true;
if (is_array($attributes['replace']) && !empty($attributes['replace'])) {
$result = ldap_mod_replace($this->conn, $subject_dn, $attributes['replace']);
}
if (!$result) {
console("Failed to replace the following attributes", $attributes['replace']);
return false;
}
if (is_array($attributes['add']) && !empty($attributes['add'])) {
$result = ldap_mod_add($this->conn, $subject_dn, $attributes['add']);
}
if (!$result) {
console("Failed to add the following attributes", $attributes['add']);
return false;
}
if (is_array($attributes['rename']) && !empty($attributes['rename'])) {
$olddn = key($attributes['rename']);
$newrdn = $attributes['rename'][$olddn];
$result = ldap_rename($this->conn, $olddn, $newrdn, NULL, true);
}
if (!$result) {
return false;
}
if ($result) {
return true;
} else {
return false;
}
}
public function user_add($attrs, $type = null)
{
if ($type == null) {
$type_str = 'user';
}
else {
$db = SQL::get_instance();
$_key = $db->fetch_assoc($db->query("SELECT `key` FROM user_types WHERE id = ?", $type));
$type_str = $_key['key'];
}
// Check if the user_type has a specific base DN specified.
$base_dn = $this->conf->get($this->domain, $type_str . "_user_base_dn");
// If not, take the regular user_base_dn
if (!$base_dn)
$base_dn = $this->conf->get($this->domain, "user_base_dn");
// If no user_base_dn either, take the user type specific from the parent
// configuration
if (!$base_dn)
$base_dn = $this->conf->get('ldap', $type_str . "_user_base_dn");
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "uid=" . $attrs['uid'] . "," . $base_dn;
return $this->_add($dn, $attrs);
}
public function user_delete($user)
{
$is_dn = ldap_explode_dn($user, 1);
if (!$is_dn) {
list($this->userid, $this->domain) = $this->_qualify_id($user);
$root_dn = $this->domain_root_dn($this->domain);
$user_dn = $this->_get_user_dn($root_dn, '(mail=' . $user . ')');
}
else {
$user_dn = $user;
}
if (!$user_dn) {
return false;
}
return $this->_delete($user_dn);
}
public function user_find_by_attribute($attribute)
{
if (empty($attribute) || !is_array($attribute) || count($attribute) > 1) {
return false;
}
if (empty($attribute[key($attribute)])) {
return false;
}
$filter = "(&";
foreach ($attribute as $key => $value) {
$filter .= "(" . $key . "=" . $value . ")";
}
$filter .= ")";
$base_dn = $this->domain_root_dn($this->domain);
$result = self::normalize_result($this->search($base_dn, $filter, array_keys($attribute)));
if (count($result) > 0) {
error_log("Results found: " . implode(', ', array_keys($result)));
return $result;
}
else {
error_log("No result");
return false;
}
}
/**
* User attributes
*
*
*/
public function user_info($user)
{
$is_dn = ldap_explode_dn($user, 1);
if (!$is_dn) {
list($this->userid, $this->domain) = $this->_qualify_id($user);
$root_dn = $this->domain_root_dn($this->domain);
$user_dn = $this->_get_user_dn($root_dn, '(mail=' . $user . ')');
}
else {
$user_dn = $user;
}
if (!$user_dn) {
return false;
}
return self::normalize_result($this->search($user_dn));
}
public function find_user_groups($member_dn)
{
error_log(__FILE__ . "(" . __LINE__ . "): " . $member_dn);
$groups = array();
$root_dn = $this->domain_root_dn($this->domain);
// TODO: Do not query for both, it's either one or the other
$entries = $this->search($root_dn, "(|" .
"(&(objectclass=groupofnames)(member=$member_dn))" .
"(&(objectclass=groupofuniquenames)(uniquemember=$member_dn))" .
")");
$entries = self::normalize_result($entries);
foreach ($entries as $entry_dn => $entry_attributes) {
$groups[] = $entry_dn;
}
return $groups;
}
public function group_add($attrs, $type = null)
{
if ($type == null) {
$type_str = 'group';
}
else {
$db = SQL::get_instance();
$_key = $db->fetch_assoc($db->query("SELECT `key` FROM group_types WHERE id = ?", $type));
$type_str = $_key['key'];
}
// Check if the user_type has a specific base DN specified.
$base_dn = $this->conf->get($type_str . "_group_base_dn");
// If not, take the regular user_base_dn
if (!$base_dn)
$base_dn = $this->conf->get("group_base_dn");
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]user_rdn_attr
$dn = "cn=" . $attrs['cn'] . "," . $base_dn;
return $this->_add($dn, $attrs);
}
public function group_info($group)
{
$is_dn = ldap_explode_dn($group, 1);
if (!$is_dn) {
$root_dn = $this->domain_root_dn($this->domain);
$group_dn = $this->_get_group_dn($root_dn, '(mail=' . $group . ')');
}
else {
$group_dn = $group;
}
if (!$group_dn) {
return false;
}
return self::normalize_result($this->search($group_dn));
}
public function group_members_list($group)
{
$is_dn = ldap_explode_dn($group, 1);
if (!$is_dn) {
$root_dn = $this->domain_root_dn($this->domain);
$group_dn = $this->_get_group_dn($root_dn, '(mail=' . $group . ')');
}
else {
$group_dn = $group;
}
if (!$group_dn) {
return false;
}
return $this->_list_group_members($group_dn);
}
/*
Translate a domain name into it's corresponding root dn.
*/
private function domain_root_dn($domain = '')
{
$conf = Conf::get_instance();
if ($domain == '') {
return false;
}
if (!$this->_connect()) {
return false;
}
error_log("Searching for domain $domain");
error_log("From domain to root dn");
if (($this->_bind($conf->get('ldap', 'bind_dn'), $conf->get('ldap', 'bind_pw'))) == false) {
error_log("WARNING: Invalid Service bind credentials supplied");
$this->_bind($conf->manager_bind_dn, $conf->manager_bind_pw);
}
// TODO: Get domain_attr from config
if (($results = ldap_search($this->conn, $conf->get('domain_base_dn'), '(associatedDomain=' . $domain . ')')) == false) {
error_log("No results?");
return false;
}
$domain = ldap_first_entry($this->conn, $results);
$domain_info = ldap_get_attributes($this->conn, $domain);
// echo "<pre>"; print_r($domain_info); echo "</pre>";
// TODO: Also very 389 specific
if (isset($domain_info['inetDomainBaseDN'][0])) {
$domain_rootdn = $domain_info['inetDomainBaseDN'][0];
}
else {
$domain_rootdn = $this->_standard_root_dn($domain_info['associatedDomain']);
}
$this->_unbind();
error_log("Using $domain_rootdn");
return $domain_rootdn;
}
private function search($base_dn, $search_filter = '(objectClass=*)', $attributes = array('*'))
{
return $this->_search($base_dn, $search_filter, $attributes);
}
private function domains_list()
{
$section = $this->conf->get('kolab', 'auth_mechanism');
$base_dn = $this->conf->get($section, 'domain_base_dn');
$filter = $this->conf->get($section, 'kolab_domain_filter');
return $this->search($base_dn, $filter);
}
private function users_list($attributes = array(), $search = array())
{
$conf = Conf::get_instance();
$base_dn = $conf->get('user_base_dn');
if (!$base_dn)
$base_dn = $conf->get('base_dn');
$filter = $conf->get('user_filter');
if (empty($attributes) || !is_array($attributes)) {
$attributes = array('*');
}
if ($s_filter = $this->_search_filter($search)) {
// join search filter with objectClass filter
$filter = '(&' . $filter . $s_filter . ')';
}
return $this->search($base_dn, $filter, $attributes);
}
private function roles_list($attributes = array(), $search = array())
{
$conf = Conf::get_instance();
$base_dn = $conf->get('base_dn');
// TODO: From config
$filter = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))";
if (empty($attributes) || !is_array($attributes)) {
$attributes = array('*');
}
if ($s_filter = $this->_search_filter($search)) {
// join search filter with objectClass filter
$filter = '(&' . $filter . $s_filter . ')';
}
return $this->search($base_dn, $filter, $attributes);
}
private function groups_list($attributes = array(), $search = array())
{
$conf = Conf::get_instance();
$base_dn = $conf->get('group_base_dn');
if (!$base_dn)
$base_dn = $conf->get('base_dn');
$filter = $conf->get('group_filter');
if (empty($attributes) || !is_array($attributes)) {
$attributes = array('*');
}
if ($s_filter = $this->_search_filter($search)) {
// join search filter with objectClass filter
$filter = '(&' . $filter . $s_filter . ')';
}
return $this->search($base_dn, $filter, $attributes);
}
public static function normalize_result($__result)
{
if (!is_array($__result)) {
return array();
}
$conf = Conf::get_instance();
$dn_attr = $conf->get($conf->get('kolab', 'auth_mechanism'), 'domain_name_attribute');
$result = array();
for ($x = 0; $x < $__result["count"]; $x++) {
$dn = $__result[$x]['dn'];
$result[$dn] = array();
for ($y = 0; $y < $__result[$x]["count"]; $y++) {
$attr = $__result[$x][$y];
if ($__result[$x][$attr]["count"] == 1) {
switch ($attr) {
case "objectclass":
$result[$dn][$attr] = strtolower($__result[$x][$attr][0]);
break;
default:
$result[$dn][$attr] = $__result[$x][$attr][0];
break;
}
}
else {
$result[$dn][$attr] = array();
for ($z = 0; $z < $__result[$x][$attr]["count"]; $z++) {
// The first result in the array is the primary domain.
if ($z == 0 && $attr == $dn_attr) {
$result[$dn]['primary_domain'] = $__result[$x][$attr][$z];
}
switch ($attr) {
case "objectclass":
$result[$dn][$attr][] = strtolower($__result[$x][$attr][$z]);
break;
default:
$result[$dn][$attr][] = $__result[$x][$attr][$z];
break;
}
}
}
}
}
return $result;
}
private function parse_attribute_level_rights($attribute_value)
{
$attribute_value = str_replace(", ", ",", $attribute_value);
$attribute_values = explode(",", $attribute_value);
$attribute_value = array();
foreach ($attribute_values as $access_right) {
$access_right_components = explode(":", $access_right);
$access_attribute = array_shift($access_right_components);
$access_value = array_shift($access_right_components);
$attribute_value[$access_attribute] = array();
for ($i = 0; $i < strlen($access_value); $i++) {
$method = $this->attribute_level_rights_map[substr($access_value, $i, 1)];
if (!in_array($method, $attribute_value[$access_attribute])) {
$attribute_value[$access_attribute][] = $method;
}
}
}
return $attribute_value;
}
private function parse_entry_level_rights($attribute_value)
{
$_attribute_value = array();
for ($i = 0; $i < strlen($attribute_value); $i++) {
$method = $this->entry_level_rights_map[substr($attribute_value, $i, 1)];
if (!in_array($method, $_attribute_value)) {
$_attribute_value[] = $method;
}
}
return $_attribute_value;
}
/**
* Result sorting callback for uasort()
*/
public function sort_result($a, $b)
{
$str1 = $a[$this->sort_result_key];
$str2 = $b[$this->sort_result_key];
return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
}
/**
* Qualify a username.
*
* Where username is 'kanarip@kanarip.com', the function will return an
* array containing 'kanarip' and 'kanarip.com'. However, where the
* username is 'kanarip', the domain name is to be assumed the
* management domain name.
*/
private function _qualify_id($username)
{
$conf = Conf::get_instance();
$username_parts = explode('@', $username);
if (count($username_parts) == 1) {
$domain_name = $conf->get('primary_domain');
}
else {
$domain_name = array_pop($username_parts);
}
return array(implode('@', $username_parts), $domain_name);
}
public function user_type_attribute_filter($type = false)
{
global $conf;
// If the user type does not exist, issue warning and continue with
// the "All attributes" array.
if (!isset($conf->user_types[$type])) {
return array('*');
}
$attributes_filter = array();
foreach ($conf->user_types[$type]['attributes'] as $key => $value) {
$attributes_filter[] = is_array($value) ? $key : $value;
}
return $attributes_filter;
}
public function user_type_search_filter($type = false)
{
global $conf;
// TODO: If the user type has not been specified we should actually
// iterate and mix and match:
//
// (|(&(type1))(&(type2)))
// If the user type does not exist, issue warning and continue with
// the "All" search filter.
if (!isset($conf->user_types[$type])) {
return "(objectClass=*)";
}
$search_filter = "(&";
// We want from user_types[$type]['attributes']['objectClasses']
foreach ($conf->user_types[$type]['attributes']['objectClass'] as $key => $value) {
$search_filter .= "(objectClass=" . $value . ")";
}
$search_filter .= ")";
print "<li>" . $search_filter;
return $search_filter;
}
/***********************************************************
************ Shortcut functions ****************
***********************************************************/
/*
Shortcut to ldap_add()
*/
private function _add($entry_dn, $attributes)
{
// Always bind with the session credentials
$this->_bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
//console("Entry DN", $entry_dn);
//console("Attributes", $attributes);
foreach ($attributes as $attr_name => $attr_value) {
if (empty($attr_value)) {
unset($attributes[$attr_name]);
}
}
if (($add_result = ldap_add($this->conn, $entry_dn, $attributes)) == false) {
// Issue warning
return false;
}
return true;
}
/**
* Shortcut to ldap_bind()
*/
private function _bind($dn, $pw)
{
$this->_connect();
if (!$this->conn || !$dn || !$pw) {
return false;
}
if ($dn == $this->bind_dn && $pw == $this->bind_pw) {
return true;
}
// TODO: Debug logging
error_log("->_bind() Binding with $dn");
$this->bind_dn = $dn;
$this->bind_pw = $pw;
if (($bind_ok = ldap_bind($this->conn, $dn, $pw)) == false) {
error_log("LDAP Error: " . $this->_errstr());
// Issue error message
return false;
}
return true;
}
/**
* Shortcut to ldap_connect()
*/
private function _connect()
{
if ($this->conn) {
return true;
}
// TODO: Debug logging
error_log("Connecting to " . $this->_ldap_server . " on port " . $this->_ldap_port);
$connection = ldap_connect($this->_ldap_server, $this->_ldap_port);
if ($connection == false) {
$this->conn = null;
// TODO: Debug logging
error_log("Not connected: " . ldap_err2str() . "(no.) " . ldap_errno());
return false;
}
$this->conn = $connection;
// TODO: Debug logging
error_log("Connected!");
return true;
}
/**
* Shortcut to ldap_delete()
*/
private function _delete($entry_dn)
{
// Always bind with the session credentials
$this->_bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
if (($delete_result = ldap_delete($this->conn, $entry_dn)) == false) {
// Issue warning
return false;
}
else {
return true;
}
}
/**
* Shortcut to ldap_disconnect()
*/
private function _disconnect()
{
if (!$this->conn) {
return true;
}
if (($result = ldap_close($this->conn)) == true) {
$this->conn = null;
$this->bind_dn = null;
$this->bind_pw = null;
return true;
}
return false;
}
/**
* Shortcut to ldap_err2str() over ldap_errno()
*/
private function _errstr()
{
if ($errno = @ldap_errno($this->conn)) {
if ($err2str = @ldap_err2str($errno)) {
return $err2str;
}
}
// Issue warning
return null;
}
/**
* Shortcut to ldap_get_entries() over ldap_list()
*
* Takes a $base_dn and $filter like ldap_list(), and returns an
* array obtained through ldap_get_entries().
*/
private function _list($base_dn, $filter)
{
if (!$this->conn) {
return null;
}
$ldap_entries = array( "count" => 0 );
if (($ldap_list = @ldap_list($this->conn, $base_dn, $filter)) == false) {
//message("LDAP Error: Could not search " . $base_dn . ": " . $this->_errstr() );
}
else {
if (($ldap_entries = @ldap_get_entries($this->conn, $ldap_list)) == false) {
//message("LDAP Error: No entries for " . $filter . " in " . $base_dn . ": " . $this->_errstr());
}
}
return $ldap_entries;
}
/**
* Shortcut to ldap_search()
*/
private function _search($base_dn, $search_filter = '(objectClass=*)', $attributes = array('*'))
{
if (!$this->_connect()) {
return false;
}
error_log("Searching $base_dn with filter: $search_filter");
error_log("Searching with user: " . $_SESSION['user']->user_bind_dn);
$this->_bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
if (($search_results = @ldap_search($this->conn, $base_dn, $search_filter, $attributes)) == false) {
//message("Could not search in " . __METHOD__ . " in " . __FILE__ . " on line " . __LINE__ . ": " . $this->_errstr());
return false;
}
if (($entries = ldap_get_entries($this->conn, $search_results)) == false) {
//message("Could not get the results of the search: " . $this->_errstr());
return false;
}
return $entries;
}
/**
* Create LDAP search filter string according to defined parameters.
*/
private function _search_filter($search)
{
if (empty($search) || !is_array($search) || empty($search['params'])) {
return null;
}
$filter = '';
foreach ((array) $search['params'] as $field => $param) {
$value = self::_quote_string($param['value']);
switch ((string)$param['type']) {
case 'prefix':
$prefix = '';
$suffix = '*';
break;
case 'suffix':
$prefix = '*';
$suffix = '';
break;
case 'exact':
$prefix = '';
$suffix = '';
break;
case 'both':
default:
$prefix = '*';
$suffix = '*';
break;
}
$filter .= "($field=$prefix" . $value . "$suffix)";
}
// join search parameters with specified operator ('OR' or 'AND')
if (count($search['params']) > 1) {
$filter = '(' . ($search['operator'] == 'AND' ? '&' : '|') . $filter . ')';
}
return $filter;
}
/**
* Shortcut to ldap_unbind()
*/
private function _unbind($yes = false, $really = false)
{
if ($yes && $really) {
if ($this->conn) {
ldap_unbind($this->conn);
}
$this->conn = null;
$this->bind_dn = null;
$this->bind_pw = null;
}
else {
// What?
//
// - attempt bind as anonymous
// - in case of fail, bind as user
}
return true;
}
/*
Utility functions
*/
/**
* Probe the root dn with the user credentials.
*
* When a list of domains is retrieved, this does not mean the user
* actually has access. Given the root dn for each domain however, we
* can in fact attempt to list / search the root dn and see if we get
* any results. If we don't, maybe this user is not authorized for the
* domain at all?
*/
private function _probe_root_dn($entry_root_dn)
{
error_log("Running for entry root dn: " . $entry_root_dn);
if (($tmpconn = ldap_connect($this->_ldap_server)) == false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
error_log("User DN: " . $_SESSION['user']->user_bind_dn);
if (($bind_success = ldap_bind($tmpconn, $_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw)) == false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
if (($list_success = ldap_list($tmpconn, $entry_root_dn, '(objectClass=*)', array('*', 'aci'))) == false) {
//message("LDAP Error: " . $this->_errstr());
return false;
}
// print_r(ldap_get_entries($tmpconn, $list_success));
/*
if (ldap_count_entries($tmpconn, $list_success) == 0) {
echo "<li>Listed things, but got no results";
return false;
}
*/
return true;
}
/**
* From a domain name, such as 'kanarip.com', create a standard root
* dn, such as 'dc=kanarip,dc=com'.
*
* As the parameter $associatedDomains, either pass it an array (such
* as may have been returned by ldap_get_entries() or perhaps
* ldap_list()), where the function will assume the first value
* ($array[0]) to be the uber-level domain name, or pass it a string
* such as 'kanarip.nl'.
*
* @return string
*/
private function _standard_root_dn($associatedDomains)
{
if (is_array($associatedDomains)) {
// Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
if ($associatedDomains['count'] > 1) {
// Issue a debug message here
$relevant_associatedDomain = $associatedDomains[0];
}
else {
$relevant_associatedDomain = $associatedDomains[0];
}
}
else {
$relevant_associatedDomain = $associatedDomains;
}
return "dc=" . implode(',dc=', explode('.', $relevant_associatedDomain));
}
private function _get_group_dn($root_dn, $search_filter)
{
// TODO: Why does this use privileged credentials?
if (($this->_bind($this->conf->get('bind_dn'), $this->conf->get('bind_pw'))) == false) {
$this->_bind($this->conf->get('manager_bind_dn'), $this->conf->get('manager_bind_pw'));
}
error_log("Searching for a group dn in $root_dn, with search filter: $search_filter");
$search_results = ldap_search($this->conn, $root_dn, $search_filter);
if (ldap_count_entries($this->conn, $search_results) == 0) {
return false;
}
if (($first_entry = ldap_first_entry($this->conn, $search_results)) == false) {
return false;
}
$group_dn = ldap_get_dn($this->conn, $first_entry);
return $group_dn;
}
private function _get_user_dn($root_dn, $search_filter)
{
// TODO: Why does this use privileged credentials?
if (($this->_bind($this->conf->get('bind_dn'), $this->conf->get('bind_pw'))) == false) {
//message("WARNING: Invalid Service bind credentials supplied");
$this->_bind($this->conf->get('manager_bind_dn'), $this->conf->get('manager_bind_pw'));
}
error_log("Searching for a user dn in $root_dn, with search filter: $search_filter");
$search_results = ldap_search($this->conn, $root_dn, $search_filter);
if (!$search_results || ldap_count_entries($this->conn, $search_results) == 0) {
//message("No entries found for the user dn in " . __METHOD__);
return false;
}
if (($first_entry = ldap_first_entry($this->conn, $search_results)) == false) {
return false;
}
$user_dn = ldap_get_dn($this->conn, $first_entry);
return $user_dn;
}
private function _list_group_members($dn, $entry = null)
{
$group_members = array();
if (is_array($entry) && in_array('objectclass', $entry)) {
if (!in_array(array('groupofnames', 'groupofuniquenames', 'groupofurls'), $entry['objectclass'])) {
error_log("Called _list_groups_members on a non-group!");
}
else {
error_log("Called list_group_members(" . $dn . ")");
}
}
$entries = self::normalize_result($this->search($dn));
//console("ENTRIES for \$dn $dn", $entries);
foreach ($entries as $entry_dn => $entry) {
if (!isset($entry['objectclass'])) {
continue;
}
foreach ($entry['objectclass'] as $objectclass) {
switch (strtolower($objectclass)) {
case "groupofnames":
$group_members = array_merge($group_members, $this->_list_group_member($entry_dn, $entry));
break;
case "groupofuniquenames":
$group_members = array_merge($group_members, $this->_list_group_uniquemember($entry_dn, $entry));
break;
case "groupofurls":
$group_members = array_merge($group_members, $this->_list_group_memberurl($entry_dn, $entry));
break;
}
}
}
return array_filter($group_members);
}
private function _list_group_member($dn, $entry)
{
error_log("Called _list_group_member(" . $dn . ")");
$group_members = array();
if (empty($entry['member'])) {
return $group_members;
}
// Use the member attributes to return an array of member ldap objects
// NOTE that the member attribute is supposed to contain a DN
foreach ($entry['member'] as $member) {
$result = @ldap_read($this->conn, $member, '(objectclass=*)');
if (!$result) {
continue;
}
$member_entry = self::normalize_result(@ldap_get_entries($this->conn, $result));
$group_members[$member] = array_pop($member_entry);
// Nested groups
// $group_group_members = $this->_list_group_members($member, $member_entry);
// if ($group_group_members) {
// $group_members = array_merge($group_group_members, $group_members);
// }
}
return array_filter($group_members);
}
private function _list_group_uniquemember($dn, $entry)
{
//console("Called _list_group_uniquemember(" . $dn . ")", $entry);
// Use the member attributes to return an array of member ldap objects
// NOTE that the member attribute is supposed to contain a DN
$group_members = array();
if (empty($entry['uniquemember'])) {
return $group_members;
}
if (is_string($entry['uniquemember'])) {
//console("uniquemember for entry is not an array");
$entry['uniquemember'] = Array( $entry['uniquemember'] );
}
foreach ($entry['uniquemember'] as $member) {
$result = @ldap_read($this->conn, $member, '(objectclass=*)');
if (!$result) {
continue;
}
$member_entry = self::normalize_result(@ldap_get_entries($this->conn, $result));
$group_members[$member] = array_pop($member_entry);
// Nested groups
$group_group_members = $this->_list_group_members($member, $member_entry);
if ($group_group_members) {
$group_members = array_merge($group_group_members, $group_members);
}
}
return array_filter($group_members);
}
private function _list_group_memberurl($dn, $entry)
{
error_log("Called _list_group_memberurl(" . $dn . ")");
// Use the member attributes to return an array of member ldap objects
// NOTE that the member attribute is supposed to contain a DN
$group_members = array();
foreach ((array)$entry['memberurl'] as $url) {
$ldap_uri_components = $this->_parse_memberurl($url);
$entries = self::normalize_result($this->search($ldap_uri_components[3], $ldap_uri_components[6]));
foreach ($entries as $entry_dn => $_entry) {
$group_members[$entry_dn] = $_entry;
error_log("Found " . $entry_dn);
// Nested group
// $group_group_members = $this->_list_group_members($entry_dn, $_entry);
// if ($group_group_members) {
// $group_members = array_merge($group_members, $group_group_members);
// }
}
}
return array_filter($group_members);
}
/**
* memberUrl attribute parser
*
* @param string $url URL string
*
* @return array URL elements
*/
private function _parse_memberurl($url)
{
error_log("Parsing URL: " . $url);
preg_match('/(.*):\/\/(.*)\/(.*)\?(.*)\?(.*)\?(.*)/', $url, $matches);
return $matches;
}
/**
* Quotes attribute value string
*
* @param string $str Attribute value
* @param bool $dn True if the attribute is a DN
*
* @return string Quoted string
*/
private static function _quote_string($str, $dn=false)
{
// take firt entry if array given
if (is_array($str)) {
$str = reset($str);
}
if ($dn) {
$replace = array(
',' => '\2c',
'=' => '\3d',
'+' => '\2b',
'<' => '\3c',
'>' => '\3e',
';' => '\3b',
"\\"=> '\5c',
'"' => '\22',
'#' => '\23'
);
}
else {
$replace = array(
'*' => '\2a',
'(' => '\28',
')' => '\29',
"\\" => '\5c',
'/' => '\2f'
);
}
return strtr($str, $replace);
}
}

File Metadata

Mime Type
text/x-php
Expires
Mon, Apr 6, 1:21 AM (2 d, 1 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831831
Default Alt Text
LDAP.php (47 KB)

Event Timeline