]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_ldap.inc
Imported Upstream version 0.1
[roundcube.git] / program / include / rcube_ldap.inc
1 <?php
2 /*
3  +-----------------------------------------------------------------------+
4  | program/include/rcube_ldap.inc                                        |
5  |                                                                       |
6  | This file is part of the RoundCube Webmail client                     |
7  | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland                 |
8  | Licensed under the GNU GPL                                            |
9  |                                                                       |
10  | PURPOSE:                                                              |
11  |   Interface to an LDAP address directory                              |
12  |                                                                       |
13  +-----------------------------------------------------------------------+
14  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
15  +-----------------------------------------------------------------------+
16
17  $Id: rcube_ldap.inc 1152 2008-02-28 20:01:57Z thomasb $
18
19 */
20
21
22 /**
23  * Model class to access an LDAP address directory
24  *
25  * @package Addressbook
26  */
27 class rcube_ldap
28 {
29   var $conn;
30   var $prop = array();
31   var $fieldmap = array();
32   
33   var $filter = '';
34   var $result = null;
35   var $ldap_result = null;
36   var $sort_col = '';
37   
38   /** public properties */
39   var $primary_key = 'ID';
40   var $readonly = true;
41   var $list_page = 1;
42   var $page_size = 10;
43   var $ready = false;
44   
45   
46   /**
47    * Object constructor
48    *
49    * @param array LDAP connection properties
50    * @param integer User-ID
51    */
52   function __construct($p)
53   {
54     $this->prop = $p;
55     
56     foreach ($p as $prop => $value)
57       if (preg_match('/^(.+)_field$/', $prop, $matches))
58         $this->fieldmap[$matches[1]] = $value;
59     
60     $this->connect();
61   }
62
63   /**
64    * PHP 4 object constructor
65    *
66    * @see  rcube_ldap::__construct()
67    */
68   function rcube_ldap($p)
69   {
70     $this->__construct($p);
71   }
72   
73
74   /**
75    * Establish a connection to the LDAP server
76    */
77   function connect()
78   {
79     if (!function_exists('ldap_connect'))
80       raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
81
82     if (is_resource($this->conn))
83       return true;
84     
85     if (!is_array($this->prop['hosts']))
86       $this->prop['hosts'] = array($this->prop['hosts']);
87
88     if (empty($this->prop['ldap_version']))
89       $this->prop['ldap_version'] = 3;
90
91     foreach ($this->prop['hosts'] as $host)
92     {
93       if ($lc = @ldap_connect($host, $this->prop['port']))
94       {
95         ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['ldap_version']);
96         $this->prop['host'] = $host;
97         $this->conn = $lc;
98         break;
99       }
100     }
101     
102     if (is_resource($this->conn))
103     {
104       $this->ready = true;
105       if (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass']))
106         $this->ready = $this->bind($this->prop['bind_dn'], $this->prop['bind_pass']);
107     }
108     else
109       raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
110   }
111
112
113   /**
114    * Bind connection with DN and password
115    *
116    * @param string Bind DN
117    * @param string Bind password
118    * @return boolean True on success, False on error
119    */
120   function bind($dn, $pass)
121   {
122     if (!$this->conn)
123       return false;
124     
125     if (@ldap_bind($this->conn, $dn, $pass))
126       return true;
127     else
128     {
129       raise_error(array(
130         'code' => ldap_errno($this->conn),
131         'type' => 'ldap',
132         'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
133       true);
134     }
135     
136     return false;
137   }
138
139
140   /**
141    * Close connection to LDAP server
142    */
143   function close()
144   {
145     if ($this->conn)
146     {
147       @ldap_unbind($this->conn);
148       $this->conn = null;
149     }
150   }
151
152
153   /**
154    * Set internal list page
155    *
156    * @param  number  Page number to list
157    * @access public
158    */
159   function set_page($page)
160   {
161     $this->list_page = (int)$page;
162   }
163
164
165   /**
166    * Set internal page size
167    *
168    * @param  number  Number of messages to display on one page
169    * @access public
170    */
171   function set_pagesize($size)
172   {
173     $this->page_size = (int)$size;
174   }
175
176
177   /**
178    * Save a search string for future listings
179    *
180    * @param string Filter string
181    */
182   function set_search_set($filter)
183   {
184     $this->filter = $filter;
185   }
186   
187   
188   /**
189    * Getter for saved search properties
190    *
191    * @return mixed Search properties used by this class
192    */
193   function get_search_set()
194   {
195     return $this->filter;
196   }
197
198
199   /**
200    * Reset all saved results and search parameters
201    */
202   function reset()
203   {
204     $this->result = null;
205     $this->ldap_result = null;
206     $this->filter = '';
207   }
208   
209   
210   /**
211    * List the current set of contact records
212    *
213    * @param  array  List of cols to show
214    * @param  int    Only return this number of records (not implemented)
215    * @return array  Indexed list of contact records, each a hash array
216    */
217   function list_records($cols=null, $subset=0)
218   {
219     // add general filter to query
220     if (!empty($this->prop['filter']))
221     {
222       $filter = $this->prop['filter'];
223       $this->set_search_set($filter);
224     }
225     
226     // exec LDAP search if no result resource is stored
227     if ($this->conn && !$this->ldap_result)
228       $this->_exec_search();
229     
230     // count contacts for this user
231     $this->result = $this->count();
232     
233     // we have a search result resource
234     if ($this->ldap_result && $this->result->count > 0)
235     {
236       if ($this->sort_col && $this->prop['scope'] !== "base")
237         @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
238         
239       $entries = ldap_get_entries($this->conn, $this->ldap_result);
240       for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
241         $this->result->add($this->_ldap2result($entries[$i]));
242     }
243
244     return $this->result;
245   }
246
247
248   /**
249    * Search contacts
250    *
251    * @param array   List of fields to search in
252    * @param string  Search value
253    * @param boolean True if results are requested, False if count only
254    * @return array  Indexed list of contact records and 'count' value
255    */
256   function search($fields, $value, $strict=false, $select=true)
257   {
258     // special treatment for ID-based search
259     if ($fields == 'ID' || $fields == $this->primary_key)
260     {
261       $ids = explode(',', $value);
262       $result = new rcube_result_set();
263       foreach ($ids as $id)
264         if ($rec = $this->get_record($id, true))
265         {
266           $result->add($rec);
267           $result->count++;
268         }
269       
270       return $result;
271     }
272     
273     $filter = '(|';
274     $wc = !$strict && $this->prop['fuzzy_search'] ? '*' : '';
275     if (is_array($this->prop['search_fields']))
276     {
277       foreach ($this->prop['search_fields'] as $k => $field)
278         $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
279     }
280     else
281     {
282       foreach ((array)$fields as $field)
283         if ($f = $this->_map_field($field))
284           $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
285     }
286     $filter .= ')';
287     
288     // avoid double-wildcard if $value is empty
289     $filter = preg_replace('/\*+/', '*', $filter);
290     
291     // add general filter to query
292     if (!empty($this->prop['filter']))
293       $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $filter . ')';
294
295     // set filter string and execute search
296     $this->set_search_set($filter);
297     $this->_exec_search();
298     
299     if ($select)
300       $this->list_records();
301     else
302       $this->result = $this->count();
303    
304     return $this->result; 
305   }
306
307
308   /**
309    * Count number of available contacts in database
310    *
311    * @return object rcube_result_set Resultset with values for 'count' and 'first'
312    */
313   function count()
314   {
315     $count = 0;
316     if ($this->conn && $this->ldap_result)
317       $count = ldap_count_entries($this->conn, $this->ldap_result);
318
319     return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
320   }
321
322
323   /**
324    * Return the last result set
325    *
326    * @return object rcube_result_set Current resultset or NULL if nothing selected yet
327    */
328   function get_result()
329   {
330     return $this->result;
331   }
332   
333   
334   /**
335    * Get a specific contact record
336    *
337    * @param mixed   Record identifier
338    * @param boolean Return as associative array
339    * @return mixed  Hash array or rcube_result_set with all record fields
340    */
341   function get_record($dn, $assoc=false)
342   {
343     $res = null;
344     if ($this->conn && $dn)
345     {
346       $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
347       $entry = @ldap_first_entry($this->conn, $this->ldap_result);
348       
349       if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
350       {
351         $res = $this->_ldap2result($rec);
352         $this->result = new rcube_result_set(1);
353         $this->result->add($res);
354       }
355     }
356
357     return $assoc ? $res : $this->result;
358   }
359   
360   
361   /**
362    * Create a new contact record
363    *
364    * @param array    Hash array with save data
365    * @return boolean The create record ID on success, False on error
366    */
367   function insert($save_cols)
368   {
369     // TODO
370     return false;
371   }
372   
373   
374   /**
375    * Update a specific contact record
376    *
377    * @param mixed Record identifier
378    * @param array Hash array with save data
379    * @return boolean True on success, False on error
380    */
381   function update($id, $save_cols)
382   {
383     // TODO    
384     return false;
385   }
386   
387   
388   /**
389    * Mark one or more contact records as deleted
390    *
391    * @param array  Record identifiers
392    * @return boolean True on success, False on error
393    */
394   function delete($ids)
395   {
396     // TODO
397     return false;
398   }
399
400
401   /**
402    * Execute the LDAP search based on the stored credentials
403    *
404    * @access private
405    */
406   function _exec_search()
407   {
408     if ($this->conn && $this->filter)
409     {
410       $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
411       $this->ldap_result = $function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
412       return true;
413     }
414     else
415       return false;
416   }
417   
418   
419   /**
420    * @access private
421    */
422   function _ldap2result($rec)
423   {
424     $out = array();
425     
426     if ($rec['dn'])
427       $out[$this->primary_key] = base64_encode($rec['dn']);
428     
429     foreach ($this->fieldmap as $rf => $lf)
430     {
431       if ($rec[$lf]['count'])
432         $out[$rf] = $rec[$lf][0];
433     }
434     
435     return $out;
436   }
437   
438   
439   /**
440    * @access private
441    */
442   function _map_field($field)
443   {
444     return $this->fieldmap[$field];
445   }
446   
447   
448   /**
449    * @static
450    */
451   function quote_string($str)
452   {
453     return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));
454   }
455
456
457 }
458
459 ?>