5100704d |
1 | <?php |
35586184 |
2 | /** |
3 | * abook_ldap_server.php |
4 | * |
6c84ba1e |
5 | * Copyright (c) 1999-2005 The SquirrelMail Project Team |
35586184 |
6 | * Licensed under the GNU GPL. For full terms see the file COPYING. |
7 | * |
981681d5 |
8 | * Address book backend for LDAP server |
9 | * |
ae4d36f7 |
10 | * LDAP filtering code by Tim Bell |
11 | * <bhat at users.sourceforge.net> (#539534) |
f8a1ed5a |
12 | * ADS limit_scope code by Michael Brown |
ae4d36f7 |
13 | * <mcb30 at users.sourceforge.net> (#1035454) |
14 | * |
981681d5 |
15 | * @version $Id$ |
16 | * @package squirrelmail |
17 | * @subpackage addressbook |
18 | */ |
19 | |
20 | /** |
35586184 |
21 | * Address book backend for LDAP server |
22 | * |
23 | * An array with the following elements must be passed to |
24 | * the class constructor (elements marked ? are optional): |
981681d5 |
25 | * <pre> |
35586184 |
26 | * host => LDAP server hostname/IP-address |
27 | * base => LDAP server root (base dn). Empty string allowed. |
28 | * ? port => LDAP server TCP port number (default: 389) |
29 | * ? charset => LDAP server charset (default: utf-8) |
30 | * ? name => Name for LDAP server (default "LDAP: hostname") |
31 | * Used to tag the result data |
32 | * ? maxrows => Maximum # of rows in search result |
ae4d36f7 |
33 | * ? filter => Filter expression to limit ldap searches |
35586184 |
34 | * ? timeout => Timeout for LDAP operations (in seconds, default: 30) |
35 | * Might not work for all LDAP libraries or servers. |
30e9932c |
36 | * ? binddn => LDAP Bind DN. |
37 | * ? bindpw => LDAP Bind Password. |
38 | * ? protocol => LDAP Bind protocol. |
ae4d36f7 |
39 | * ? limit_scope => Limits scope to base DN. |
981681d5 |
40 | * </pre> |
35586184 |
41 | * NOTE. This class should not be used directly. Use the |
42 | * "AddressBook" class instead. |
d6c32258 |
43 | * @package squirrelmail |
a9d318b0 |
44 | * @subpackage addressbook |
35586184 |
45 | */ |
35586184 |
46 | class abook_ldap_server extends addressbook_backend { |
981681d5 |
47 | /** |
48 | * @var string backend type |
49 | */ |
06b4facd |
50 | var $btype = 'remote'; |
981681d5 |
51 | /** |
52 | * @var string backend name |
53 | */ |
06b4facd |
54 | var $bname = 'ldap_server'; |
55 | |
56 | /* Parameters changed by class */ |
981681d5 |
57 | /** |
58 | * @var string displayed name |
59 | */ |
06b4facd |
60 | var $sname = 'LDAP'; /* Service name */ |
981681d5 |
61 | /** |
62 | * @var string LDAP server name or address or url |
63 | */ |
64 | var $server = ''; |
65 | /** |
66 | * @var integer LDAP server port |
67 | */ |
68 | var $port = 389; |
69 | /** |
70 | * @var string LDAP base DN |
71 | */ |
72 | var $basedn = ''; |
73 | /** |
74 | * @var string charset used for entries in LDAP server |
75 | */ |
76 | var $charset = 'utf-8'; |
77 | /** |
78 | * @var object PHP LDAP link ID |
79 | */ |
80 | var $linkid = false; |
81 | /** |
82 | * @var bool True if LDAP server is bound |
83 | */ |
84 | var $bound = false; |
85 | /** |
86 | * @var integer max rows in result |
87 | */ |
88 | var $maxrows = 250; |
ae4d36f7 |
89 | /** |
90 | * @var string ldap filter |
91 | * @since 1.5.1 |
92 | */ |
93 | var $filter = ''; |
981681d5 |
94 | /** |
95 | * @var integer timeout of LDAP operations (in seconds) |
96 | */ |
97 | var $timeout = 30; |
98 | /** |
99 | * @var string DN to bind to (non-anonymous bind) |
100 | * @since 1.5.0 and 1.4.3 |
101 | */ |
102 | var $binddn = ''; |
103 | /** |
104 | * @var string password to bind with (non-anonymous bind) |
105 | * @since 1.5.0 and 1.4.3 |
106 | */ |
107 | var $bindpw = ''; |
108 | /** |
109 | * @var integer protocol used to connect to ldap server |
110 | * @since 1.5.0 and 1.4.3 |
111 | */ |
112 | var $protocol = ''; |
ae4d36f7 |
113 | /** |
114 | * @var boolean limits scope to base dn |
115 | * @since 1.5.1 |
116 | */ |
117 | var $limit_scope = false; |
981681d5 |
118 | |
119 | /** |
120 | * Constructor. Connects to database |
121 | * @param array connection options |
122 | */ |
06b4facd |
123 | function abook_ldap_server($param) { |
124 | if(!function_exists('ldap_connect')) { |
ae4d36f7 |
125 | $this->set_error(_("PHP install does not have LDAP support.")); |
06b4facd |
126 | return; |
127 | } |
128 | if(is_array($param)) { |
129 | $this->server = $param['host']; |
130 | $this->basedn = $param['base']; |
ae4d36f7 |
131 | |
132 | if(!empty($param['port'])) |
06b4facd |
133 | $this->port = $param['port']; |
ae4d36f7 |
134 | |
135 | if(!empty($param['charset'])) |
06b4facd |
136 | $this->charset = strtolower($param['charset']); |
ae4d36f7 |
137 | |
138 | if(isset($param['maxrows'])) |
06b4facd |
139 | $this->maxrows = $param['maxrows']; |
ae4d36f7 |
140 | |
141 | if(isset($param['timeout'])) |
06b4facd |
142 | $this->timeout = $param['timeout']; |
ae4d36f7 |
143 | |
144 | if(isset($param['binddn'])) |
30e9932c |
145 | $this->binddn = $param['binddn']; |
ae4d36f7 |
146 | |
147 | if(isset($param['bindpw'])) |
30e9932c |
148 | $this->bindpw = $param['bindpw']; |
ae4d36f7 |
149 | |
150 | if(isset($param['protocol'])) |
30e9932c |
151 | $this->protocol = $param['protocol']; |
ae4d36f7 |
152 | |
153 | if(isset($param['filter'])) |
154 | $this->filter = trim($param['filter']); |
155 | |
156 | if(isset($param['limit_scope'])) |
157 | $this->limit_scope = $param['limit_scope']; |
158 | |
06b4facd |
159 | if(empty($param['name'])) { |
160 | $this->sname = 'LDAP: ' . $param['host']; |
ae4d36f7 |
161 | } else { |
06b4facd |
162 | $this->sname = $param['name']; |
163 | } |
62f7daa5 |
164 | |
ae4d36f7 |
165 | /* |
166 | * don't open LDAP server on addressbook_init(), |
167 | * open ldap connection only on search. Speeds up |
168 | * addressbook_init() call. |
169 | */ |
170 | // $this->open(true); |
06b4facd |
171 | } else { |
172 | $this->set_error('Invalid argument to constructor'); |
173 | } |
174 | } |
175 | |
176 | |
981681d5 |
177 | /** |
178 | * Open the LDAP server. |
179 | * @param bool $new is it a new connection |
180 | * @return bool |
181 | */ |
06b4facd |
182 | function open($new = false) { |
183 | $this->error = ''; |
62f7daa5 |
184 | |
06b4facd |
185 | /* Connection is already open */ |
186 | if($this->linkid != false && !$new) { |
187 | return true; |
188 | } |
62f7daa5 |
189 | |
06b4facd |
190 | $this->linkid = @ldap_connect($this->server, $this->port); |
191 | if(!$this->linkid) { |
ae4d36f7 |
192 | if(function_exists('ldap_error') && is_resource($this->linkid)) { |
62f7daa5 |
193 | return $this->set_error(ldap_error($this->linkid)); |
06b4facd |
194 | } else { |
195 | return $this->set_error('ldap_connect failed'); |
196 | } |
197 | } |
62f7daa5 |
198 | |
981681d5 |
199 | if(!empty($this->protocol)) { |
30e9932c |
200 | if(!@ldap_set_option($this->linkid, LDAP_OPT_PROTOCOL_VERSION, $this->protocol)) { |
201 | if(function_exists('ldap_error')) { |
202 | return $this->set_error(ldap_error($this->linkid)); |
203 | } else { |
204 | return $this->set_error('ldap_set_option failed'); |
981681d5 |
205 | } |
206 | } |
207 | } |
30e9932c |
208 | |
ae4d36f7 |
209 | if(!empty($this->limit_scope) && $this->limit_scope) { |
210 | if(empty($this->protocol) || intval($this->protocol) < 3) { |
211 | return $this->set_error('limit_scope requires protocol >= 3'); |
212 | } |
213 | // See http://msdn.microsoft.com/library/en-us/ldap/ldap/ldap_server_domain_scope_oid.asp |
214 | $ctrl = array ( "oid" => "1.2.840.113556.1.4.1339", "iscritical" => TRUE ); |
215 | if(!@ldap_set_option($this->linkid, LDAP_OPT_SERVER_CONTROLS, array($ctrl))) { |
216 | if(function_exists('ldap_error')) { |
217 | return $this->set_error(ldap_error($this->linkid)); |
218 | } else { |
219 | return $this->set_error('limit domain scope failed'); |
220 | } |
221 | } |
222 | } |
223 | |
30e9932c |
224 | if(!empty($this->binddn)) { |
225 | if(!@ldap_bind($this->linkid, $this->binddn, $this->bindpw)) { |
226 | if(function_exists('ldap_error')) { |
227 | return $this->set_error(ldap_error($this->linkid)); |
228 | } else { |
229 | return $this->set_error('authenticated ldap_bind failed'); |
230 | } |
231 | } |
232 | } else { |
981681d5 |
233 | if(!@ldap_bind($this->linkid)) { |
234 | if(function_exists('ldap_error')) { |
235 | return $this->set_error(ldap_error($this->linkid)); |
236 | } else { |
237 | return $this->set_error('anonymous ldap_bind failed'); |
238 | } |
239 | } |
06b4facd |
240 | } |
30e9932c |
241 | |
06b4facd |
242 | $this->bound = true; |
62f7daa5 |
243 | |
06b4facd |
244 | return true; |
245 | } |
246 | |
981681d5 |
247 | /** |
248 | * Encode string to the charset used by this LDAP server |
249 | * @param string string that has to be encoded |
250 | * @return string encoded string |
251 | */ |
06b4facd |
252 | function charset_encode($str) { |
b64dd897 |
253 | global $default_charset; |
254 | if($this->charset != $default_charset) { |
255 | return charset_convert($default_charset,$str,$this->charset,false); |
06b4facd |
256 | } else { |
257 | return $str; |
258 | } |
259 | } |
260 | |
981681d5 |
261 | /** |
b64dd897 |
262 | * Decode from charset used by this LDAP server to charset used by translation |
981681d5 |
263 | * |
598294a7 |
264 | * Uses SquirrelMail charset_decode functions |
981681d5 |
265 | * @param string string that has to be decoded |
266 | * @return string decoded string |
267 | */ |
06b4facd |
268 | function charset_decode($str) { |
981681d5 |
269 | global $default_charset; |
270 | if ($this->charset != $default_charset) { |
b64dd897 |
271 | return charset_convert($this->charset,$str,$default_charset,false); |
06b4facd |
272 | } else { |
273 | return $str; |
274 | } |
275 | } |
276 | |
d58ed98f |
277 | /** |
278 | * Sanitizes ldap search strings. |
279 | * See rfc2254 |
280 | * @link http://www.faqs.org/rfcs/rfc2254.html |
ae4d36f7 |
281 | * @since 1.5.1 and 1.4.5 |
d58ed98f |
282 | * @param string $string |
283 | * @return string sanitized string |
284 | */ |
285 | function ldapspecialchars($string) { |
286 | $sanitized=array('\\' => '\5c', |
287 | '*' => '\2a', |
288 | '(' => '\28', |
289 | ')' => '\29', |
290 | "\x00" => '\00'); |
291 | |
292 | return str_replace(array_keys($sanitized),array_values($sanitized),$string); |
293 | } |
62f7daa5 |
294 | |
06b4facd |
295 | /* ========================== Public ======================== */ |
296 | |
981681d5 |
297 | /** |
298 | * Search the LDAP server |
299 | * @param string $expr search expression |
300 | * @return array search results |
301 | */ |
06b4facd |
302 | function search($expr) { |
06b4facd |
303 | /* To be replaced by advanded search expression parsing */ |
304 | if(is_array($expr)) return false; |
62f7daa5 |
305 | |
06b4facd |
306 | /* Encode the expression */ |
307 | $expr = $this->charset_encode($expr); |
d58ed98f |
308 | |
309 | /* |
f8a1ed5a |
310 | * allow use of one asterisk in search. |
d58ed98f |
311 | * Don't allow any ldap special chars if search is different |
312 | */ |
313 | if($expr!='*') { |
314 | $expr = '*' . $this->ldapspecialchars($expr) . '*'; |
06b4facd |
315 | } |
ae4d36f7 |
316 | $expression = "(cn=$expr)"; |
317 | |
318 | if ($this->filter!='') |
319 | $expression = '(&' . $this->filter . $expression . ')'; |
62f7daa5 |
320 | |
06b4facd |
321 | /* Make sure connection is there */ |
322 | if(!$this->open()) { |
323 | return false; |
324 | } |
62f7daa5 |
325 | |
cccfa9c2 |
326 | $sret = @ldap_search($this->linkid, $this->basedn, $expression, |
981681d5 |
327 | array('dn', 'o', 'ou', 'sn', 'givenname', 'cn', 'mail'), |
cccfa9c2 |
328 | 0, $this->maxrows, $this->timeout); |
62f7daa5 |
329 | |
06b4facd |
330 | /* Should get error from server using the ldap_error() function, |
331 | * but it only exist in the PHP LDAP documentation. */ |
332 | if(!$sret) { |
333 | if(function_exists('ldap_error')) { |
62f7daa5 |
334 | return $this->set_error(ldap_error($this->linkid)); |
06b4facd |
335 | } else { |
62f7daa5 |
336 | return $this->set_error('ldap_search failed'); |
06b4facd |
337 | } |
338 | } |
62f7daa5 |
339 | |
06b4facd |
340 | if(@ldap_count_entries($this->linkid, $sret) <= 0) { |
341 | return array(); |
342 | } |
62f7daa5 |
343 | |
06b4facd |
344 | /* Get results */ |
345 | $ret = array(); |
346 | $returned_rows = 0; |
347 | $res = @ldap_get_entries($this->linkid, $sret); |
348 | for($i = 0 ; $i < $res['count'] ; $i++) { |
349 | $row = $res[$i]; |
62f7daa5 |
350 | |
06b4facd |
351 | /* Extract data common for all e-mail addresses |
352 | * of an object. Use only the first name */ |
353 | $nickname = $this->charset_decode($row['dn']); |
354 | $fullname = $this->charset_decode($row['cn'][0]); |
62f7daa5 |
355 | |
06b4facd |
356 | if(!empty($row['ou'][0])) { |
357 | $label = $this->charset_decode($row['ou'][0]); |
358 | } |
359 | else if(!empty($row['o'][0])) { |
360 | $label = $this->charset_decode($row['o'][0]); |
361 | } else { |
362 | $label = ''; |
363 | } |
62f7daa5 |
364 | |
06b4facd |
365 | if(empty($row['givenname'][0])) { |
366 | $firstname = ''; |
367 | } else { |
368 | $firstname = $this->charset_decode($row['givenname'][0]); |
369 | } |
62f7daa5 |
370 | |
06b4facd |
371 | if(empty($row['sn'][0])) { |
372 | $surname = ''; |
373 | } else { |
374 | $surname = $this->charset_decode($row['sn'][0]); |
375 | } |
62f7daa5 |
376 | |
06b4facd |
377 | /* Add one row to result for each e-mail address */ |
378 | if(isset($row['mail']['count'])) { |
379 | for($j = 0 ; $j < $row['mail']['count'] ; $j++) { |
380 | array_push($ret, array('nickname' => $nickname, |
381 | 'name' => $fullname, |
382 | 'firstname' => $firstname, |
383 | 'lastname' => $surname, |
384 | 'email' => $row['mail'][$j], |
385 | 'label' => $label, |
06b4facd |
386 | 'backend' => $this->bnum, |
387 | 'source' => &$this->sname)); |
62f7daa5 |
388 | |
06b4facd |
389 | // Limit number of hits |
390 | $returned_rows++; |
62f7daa5 |
391 | if(($returned_rows >= $this->maxrows) && |
06b4facd |
392 | ($this->maxrows > 0) ) { |
393 | ldap_free_result($sret); |
394 | return $ret; |
395 | } |
396 | |
397 | } // for($j ...) |
398 | |
399 | } // isset($row['mail']['count']) |
62f7daa5 |
400 | |
06b4facd |
401 | } |
62f7daa5 |
402 | |
06b4facd |
403 | ldap_free_result($sret); |
404 | return $ret; |
405 | } /* end search() */ |
406 | |
407 | |
981681d5 |
408 | /** |
409 | * List all entries present in LDAP server |
06b4facd |
410 | * |
981681d5 |
411 | * If you run a tiny LDAP server and you want the "List All" button |
412 | * to show EVERYONE, disable first return call and enable the second one. |
413 | * Remember that maxrows setting might limit list of returned entries. |
06b4facd |
414 | * |
981681d5 |
415 | * Careful with this -- it could get quite large for big sites. |
416 | * @return array all entries in ldap server |
417 | */ |
418 | function list_addr() { |
419 | return array(); |
420 | // return $this->search('*'); |
421 | } |
06b4facd |
422 | } |
62f7daa5 |
423 | ?> |