summaryrefslogtreecommitdiff
path: root/program/include/rcube_ldap.inc
diff options
context:
space:
mode:
Diffstat (limited to 'program/include/rcube_ldap.inc')
-rw-r--r--program/include/rcube_ldap.inc561
1 files changed, 369 insertions, 192 deletions
diff --git a/program/include/rcube_ldap.inc b/program/include/rcube_ldap.inc
index 7cb9dee53..06a99ad0b 100644
--- a/program/include/rcube_ldap.inc
+++ b/program/include/rcube_ldap.inc
@@ -5,255 +5,432 @@
| program/include/rcube_ldap.inc |
| |
| This file is part of the RoundCube Webmail client |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland |
+ | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
- | Manage an LDAP connection |
+ | Interface to an LDAP address directory |
| |
+-----------------------------------------------------------------------+
- | Author: Jeremy Jongsma <jeremy@jongsma.org> |
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
-require_once("bugs.inc");
-
class rcube_ldap
- {
+{
var $conn;
- var $host;
- var $port;
- var $protocol;
- var $base_dn;
- var $bind_dn;
- var $bind_pass;
-
- // PHP 5 constructor
- function __construct()
- {
- }
+ var $prop = array();
+ var $fieldmap = array();
+
+ var $filter = '';
+ var $result = null;
+ var $ldap_result = null;
+ var $sort_col = '';
+
+ /** public properties */
+ var $primary_key = 'ID';
+ var $readonly = true;
+ var $list_page = 1;
+ var $page_size = 10;
+ var $ready = false;
+
+
+ /**
+ * Object constructor
+ *
+ * @param array LDAP connection properties
+ * @param integer User-ID
+ */
+ function __construct($p)
+ {
+ $this->prop = $p;
+
+ foreach ($p as $prop => $value)
+ if (preg_match('/^(.+)_field$/', $prop, $matches))
+ $this->fieldmap[$matches[1]] = $value;
+
+ // $this->filter = "(dn=*)";
+ $this->connect();
+ }
- // PHP 4 constructor
- function rcube_ldap()
- {
- $this->__construct();
- }
+ /**
+ * PHP 4 object constructor
+ *
+ * @see rcube_ldap::__construct
+ */
+ function rcube_ldap($p)
+ {
+ $this->__construct($p);
+ }
+
- function connect($hosts, $port=389, $protocol=3)
- {
+ /**
+ * Establish a connection to the LDAP server
+ */
+ function connect()
+ {
if (!function_exists('ldap_connect'))
- raise_error(array("type" => "ldap",
- "message" => "No ldap support in this installation of php."),
- TRUE);
+ raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
if (is_resource($this->conn))
- return TRUE;
+ return true;
- if (!is_array($hosts))
- $hosts = array($hosts);
+ if (!is_array($this->prop['hosts']))
+ $this->prop['hosts'] = array($this->prop['hosts']);
- foreach ($hosts as $host)
+ foreach ($this->prop['hosts'] as $host)
+ {
+ if ($lc = @ldap_connect($host, $this->prop['port']))
{
- if ($lc = @ldap_connect($host, $port))
- {
- @ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $protocol);
- $this->host = $host;
- $this->port = $port;
- $this->protocol = $protocol;
+ ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['port']);
+ $this->prop['host'] = $host;
$this->conn = $lc;
- return TRUE;
- }
+ break;
}
-
- if (!is_resource($this->conn))
- raise_error(array("type" => "ldap",
- "message" => "Could not connect to any LDAP server, tried $host:$port last"),
- TRUE);
}
+
+ if (is_resource($this->conn))
+ $this->ready = true;
+ else
+ raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
+ }
- function close()
- {
- if ($this->conn)
- {
- if (@ldap_unbind($this->conn))
- return TRUE;
- else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Could not close connection to LDAP server: ".ldap_error($this->conn)),
- TRUE);
- }
- return FALSE;
- }
- // Merge with connect()?
+ /**
+ * Merge with connect()?
+ */
function bind($dn=null, $pass=null)
- {
+ {
if ($this->conn)
- {
+ {
if ($dn)
+ {
if (@ldap_bind($this->conn, $dn, $pass))
- return TRUE;
+ return true;
else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
- TRUE);
+ raise_error(array('code' => ldap_errno($this->conn),
+ 'type' => 'ldap',
+ 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+ true);
+ }
else
+ {
if (@ldap_bind($this->conn))
- return TRUE;
+ return true;
else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Anonymous bind failed: ".ldap_error($this->conn)),
- TRUE);
- }
+ raise_error(array('code' => ldap_errno($this->conn),
+ 'type' => 'ldap',
+ 'message' => "Anonymous bind failed: ".ldap_error($this->conn)),
+ true);
+ }
+ }
else
- raise_error(array("type" => "ldap",
- "message" => "Attempted bind on nonexistent connection"), TRUE);
- return FALSE;
+ raise_error(array('type' => 'ldap', 'message' => "Attempted bind on nonexistent connection"), true);
+
+ return false;
}
- function count($base, $filter=null, $attributes=null, $scope="sub")
- {
+
+ /**
+ * Close connection to LDAP server
+ */
+ function close()
+ {
if ($this->conn)
- {
- if ($scope === 'sub')
- $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
- else if ($scope === 'one')
- $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
- else if ($scope === 'base')
- $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
- if ($sr)
- return @ldap_count_entries($this->conn, $sr);
- }
- else
- raise_error(array("type" => "ldap",
- "message" => "Attempted count search on nonexistent connection"), TRUE);
- return FALSE;
+ @ldap_unbind($this->conn);
+ }
+
+
+ /**
+ * Set internal list page
+ *
+ * @param number Page number to list
+ * @access public
+ */
+ function set_page($page)
+ {
+ $this->list_page = (int)$page;
+ }
+
+
+ /**
+ * Set internal page size
+ *
+ * @param number Number of messages to display on one page
+ * @access public
+ */
+ function set_pagesize($size)
+ {
+ $this->page_size = (int)$size;
+ }
+
+
+ /**
+ * Save a search string for future listings
+ *
+ * @param string ??
+ */
+ function set_search_set($filter)
+ {
+ $this->filter = $filter;
+ }
+
+
+ /**
+ * Getter for saved search properties
+ *
+ * @return mixed Search properties used by this class
+ */
+ function get_search_set()
+ {
+ return $this->filter;
+ }
+
+
+ /**
+ * Reset all saved results and search parameters
+ */
+ function reset()
+ {
+ $this->result = null;
+ $this->ldap_result = null;
+ $this->filter = '';
+ }
+
+
+ /**
+ * List the current set of contact records
+ *
+ * @param array List of cols to show
+ * @return array Indexed list of contact records, each a hash array
+ */
+ function list_records($cols=null, $subset=0)
+ {
+ // exec LDAP search if no result resource is stored
+ if ($this->conn && !$this->ldap_result)
+ $this->_exec_search();
+
+ // count contacts for this user
+ $this->result = $this->count();
+
+ // we have a search result resource
+ if ($this->ldap_result && $this->result->count > 0)
+ {
+ if ($this->sort_col && $this->prop['scope'] !== "base")
+ @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
+
+ $entries = ldap_get_entries($this->conn, $this->ldap_result);
+ for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
+ $this->result->add($this->_ldap2result($entries[$i]));
}
- function search($base, $filter=null, $attributes=null, $scope='sub', $sort=null, $limit=0)
+ return $this->result;
+ }
+
+
+ /**
+ * Search contacts
+ *
+ * @param array List of fields to search in
+ * @param string Search value
+ * @param boolean True if results are requested, False if count only
+ * @return Indexed list of contact records and 'count' value
+ */
+ function search($fields, $value, $select=true)
+ {
+ // special treatment for ID-based search
+ if ($fields == 'ID' || $fields == $this->primary_key)
{
- if ($this->conn)
- {
- if ($scope === 'sub')
- $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
- else if ($scope === 'one')
- $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
- else if ($scope === 'base')
- $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
- if ($sr)
+ $ids = explode(',', $value);
+ $result = new rcube_result_set();
+ foreach ($ids as $id)
+ if ($rec = $this->get_record($id, true))
{
- if ($sort && $scope !== "base")
- {
- if (is_array($sort))
- {
- // Start from the end so first sort field has highest priority
- $sortfields = array_reverse($sort);
- foreach ($sortfields as $sortfield)
- @ldap_sort($this->conn, $sr, $sortfield);
- }
- else
- @ldap_sort($this->conn, $sr, $sort);
- }
- return @ldap_get_entries($this->conn, $sr);
+ $result->add($rec);
+ $result->count++;
}
- }
- else
- raise_error(array("type" => "ldap",
- "message" => "Attempted search on nonexistent connection"), TRUE);
- return FALSE;
+
+ return $result;
}
-
- function add($dn, $object)
+
+ $filter = '(|';
+ $wc = $this->prop['fuzzy_search'] ? '*' : '';
+ if (is_array($this->prop['search_fields']))
{
- if ($this->conn)
- {
- if (@ldap_add($this->conn, $dn, $object))
- return TRUE;
- else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Add object failed: ".ldap_error($this->conn)),
- TRUE);
- }
+ foreach ($this->prop['search_fields'] as $k => $field)
+ $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
+ }
else
- raise_error(array("type" => "ldap",
- "message" => "Add object faile: no connection"),
- TRUE);
- return FALSE;
+ {
+ foreach ((array)$fields as $field)
+ if ($f = $this->_map_field($field))
+ $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
}
+ $filter .= ')';
- function modify($dn, $object)
- {
- if ($this->conn)
- {
- if (@ldap_modify($this->conn, $dn, $object))
- return TRUE;
- else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Modify object failed: ".ldap_error($this->conn)),
- TRUE);
- }
+ // set filter string and execute search
+ $this->set_search_set($filter);
+ $this->_exec_search();
+
+ if ($select)
+ $this->list_records();
else
- raise_error(array("type" => "ldap",
- "message" => "Modify object failed: no connection"),
- TRUE);
- return FALSE;
- }
+ $this->result = $this->count();
+
+ return $this->result;
+ }
- function rename($dn, $newrdn, $parentdn)
- {
- if ($this->protocol < 3)
- {
- raise_error(array("type" => "ldap",
- "message" => "rename() support requires LDAPv3 or above "),
- TRUE);
- return FALSE;
- }
- if ($this->conn)
+ /**
+ * Count number of available contacts in database
+ *
+ * @return Result array with values for 'count' and 'first'
+ */
+ function count()
+ {
+ $count = 0;
+ if ($this->conn && $this->ldap_result)
+ $count = ldap_count_entries($this->conn, $this->ldap_result);
+
+ return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
+ }
+
+
+ /**
+ * Return the last result set
+ *
+ * @return Result array or NULL if nothing selected yet
+ */
+ function get_result()
+ {
+ return $this->result;
+ }
+
+
+ /**
+ * Get a specific contact record
+ *
+ * @param mixed record identifier
+ * @return Hash array with all record fields or False if not found
+ */
+ function get_record($dn, $assoc=false)
+ {
+ $res = null;
+ if ($this->conn && $dn)
+ {
+ $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
+ $entry = @ldap_first_entry($this->conn, $this->ldap_result);
+
+ if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
{
- if (@ldap_rename($this->conn, $dn, $newrdn, $parentdn, TRUE))
- return TRUE;
- else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Rename object failed: ".ldap_error($this->conn)),
- TRUE);
+ $res = $this->_ldap2result($rec);
+ $this->result = new rcube_result_set(1);
+ $this->result->add($res);
}
- else
- raise_error(array("type" => "ldap",
- "message" => "Rename object failed: no connection"),
- TRUE);
- return FALSE;
}
- function delete($dn)
+ return $assoc ? $res : $this->result;
+ }
+
+
+ /**
+ * Create a new contact record
+ *
+ * @param array Assoziative array with save data
+ * @return The create record ID on success, False on error
+ */
+ function insert($save_cols)
+ {
+ // TODO
+ return false;
+ }
+
+
+ /**
+ * Update a specific contact record
+ *
+ * @param mixed Record identifier
+ * @param array Assoziative array with save data
+ * @return True on success, False on error
+ */
+ function update($id, $save_cols)
+ {
+ // TODO
+ return false;
+ }
+
+
+ /**
+ * Mark one or more contact records as deleted
+ *
+ * @param array Record identifiers
+ */
+ function delete($ids)
+ {
+ // TODO
+ return false;
+ }
+
+
+ /**
+ * Execute the LDAP search based on the stored credentials
+ *
+ * @private
+ */
+ function _exec_search()
+ {
+ if ($this->conn && $this->filter)
{
- if ($this->conn)
- {
- if (@ldap_delete($this->conn, $dn))
- return TRUE;
- else
- raise_error(array("code" => ldap_errno($this->conn),
- "type" => "ldap",
- "message" => "Delete object failed: ".ldap_error($this->conn)),
- TRUE);
- }
+ $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
+ $this->ldap_result = @$function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
+ return true;
+ }
else
- raise_error(array("type" => "ldap",
- "message" => "Delete object failed: no connection"),
- TRUE);
- return FALSE;
+ return false;
+ }
+
+
+ /**
+ * @private
+ */
+ function _ldap2result($rec)
+ {
+ $out = array();
+
+ if ($rec['dn'])
+ $out[$this->primary_key] = base64_encode($rec['dn']);
+
+ foreach ($this->fieldmap as $rf => $lf)
+ {
+ if ($rec[$lf]['count'])
+ $out[$rf] = $rec[$lf][0];
}
-
+
+ return $out;
+ }
+
+
+ /**
+ * @private
+ */
+ function _map_field($field)
+ {
+ return $this->fieldmap[$field];
}
+
+
+ /**
+ * @static
+ */
+ function quote_string($str)
+ {
+ return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));
+ }
+
+
+}
-// vi: et ts=2 sw=2
-?>
+?> \ No newline at end of file