1 <?php
2 /*
3 * This code is part of GOsa (http://www.gosa-project.org)
4 * Copyright (C) 2003-2008 GONICUS GmbH
5 * Copyright (C) 2003 Alejandro Escanero Blanco <aescanero@chaosdimension.org>
6 * Copyright (C) 1998 Eric Kilfoil <eric@ipass.net>
7 *
8 * ID: $$Id$$
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
25 define("ALREADY_EXISTING_ENTRY",-10001);
26 define("UNKNOWN_TOKEN_IN_LDIF_FILE",-10002);
27 define("NO_FILE_UPLOADED",10003);
28 define("INSERT_OK",10000);
29 define("SPECIALS_OVERRIDE", TRUE);
31 class LDAP
32 {
33 public static $characterMap = NULL;
35 var $hascon =false;
36 var $reconnect=false;
37 var $tls = false;
38 var $cid;
39 var $hasres = array();
40 var $sr = array();
41 var $re = array();
42 var $basedn ="";
43 var $start = array(); // 0 if we are fetching the first entry, otherwise 1
44 var $error = ""; // Any error messages to be returned can be put here
45 var $srp = 0;
46 var $objectClasses = array(); // Information read from slapd.oc.conf
47 var $binddn = "";
48 var $bindpw = "";
49 var $hostname = "";
50 var $follow_referral = FALSE;
51 var $referrals= array();
52 var $max_ldap_query_time = 0; // 0, empty or negative values will disable this check
54 function LDAP($binddn,$bindpw, $hostname, $follow_referral= FALSE, $tls= FALSE)
55 {
56 global $config;
57 $this->follow_referral= $follow_referral;
58 $this->tls=$tls;
59 $this->binddn=LDAP::convert($binddn);
61 $this->bindpw=$bindpw;
62 $this->hostname=$hostname;
64 /* Check if MAX_LDAP_QUERY_TIME is defined */
65 if(is_object($config) && $config->get_cfg_value("core","ldapMaxQueryTime") != ""){
66 $str = $config->get_cfg_value("core","ldapMaxQueryTime");
67 $this->max_ldap_query_time = (float)($str);
68 }
70 $this->connect();
72 // Get detected character mapping
73 if(LDAP::$characterMap == NULL || TRUE){
74 LDAP::$characterMap = detectLdapSpecialCharHandling();
75 }
76 }
79 function getSearchResource()
80 {
81 $this->sr[$this->srp]= NULL;
82 $this->start[$this->srp]= 0;
83 $this->hasres[$this->srp]= false;
84 return $this->srp++;
85 }
88 /* Function to replace all problematic characters inside a DN by \001XX, where
89 \001 is decoded to chr(1) [ctrl+a]. It is not impossible, but very unlikely
90 that this character is inside a DN.
92 Currently used codes:
93 , => CO
94 \2C => CO
95 ( => OB
96 ) => CB
97 / => SL
98 \22 => DQ */
99 static function convert($dn)
100 {
101 if (SPECIALS_OVERRIDE == TRUE){
102 $tmp= preg_replace(array("/\\\\,/", "/\\\\2C/", "/\(/", "/\)/", "/\//", "/\\\\22/", '/\\\\"/'),
103 array("\001CO", "\001CO", "\001OB", "\001CB", "\001SL", "\001DQ", "\001DQ"),
104 $dn);
105 return (preg_replace('/,\s+/', ',', $tmp));
106 } else {
107 return ($dn);
108 }
109 }
112 /* Function to fix all problematic characters inside a DN by replacing \001XX
113 codes to their original values. See "convert" for mor information.
114 ',' characters are always expanded to \, (not \2C), since all tested LDAP
115 servers seem to take it the correct way. */
116 static function fix($dn)
117 {
118 if (SPECIALS_OVERRIDE == TRUE){
120 print_a(LDAP::$characterMap);
122 return (preg_replace(array("/\001CO/", "/\001OB/", "/\001CB/", "/\001SL/", "/\001DQ/"),
123 array("\,", "(", ")", "/", '\"'),
124 $dn));
125 } else {
126 return ($dn);
127 }
128 }
130 /* Function to fix problematic characters in DN's that are used for search
131 requests. I.e. member=.... */
132 static function prepare4filter($dn)
133 {
134 $fixed= normalizeLdap(str_replace('\\\\', '\\\\\\', LDAP::fix($dn)));
135 return str_replace('\\,', '\\\\,', $fixed);
136 }
139 function connect()
140 {
141 $this->hascon=false;
142 $this->reconnect=false;
143 if ($this->cid= @ldap_connect($this->hostname)) {
144 @ldap_set_option($this->cid, LDAP_OPT_PROTOCOL_VERSION, 3);
145 if (function_exists("ldap_set_rebind_proc") && $this->follow_referral) {
146 @ldap_set_option($this->cid, LDAP_OPT_REFERRALS, 1);
147 @ldap_set_rebind_proc($this->cid, array(&$this, "rebind"));
148 }
149 if (function_exists("ldap_start_tls") && $this->tls){
150 @ldap_start_tls($this->cid);
151 }
153 $this->error = "No Error";
154 if ($bid = @ldap_bind($this->cid, LDAP::fix($this->binddn), $this->bindpw)) {
155 $this->error = "Success";
156 $this->hascon=true;
157 } else {
158 if ($this->reconnect){
159 if ($this->error != "Success"){
160 $this->error = "Could not rebind to " . $this->binddn;
161 }
162 } else {
163 $this->error = "Could not bind to " . $this->binddn;
164 }
165 }
166 } else {
167 $this->error = "Could not connect to LDAP server";
168 }
169 }
171 function rebind($ldap, $referral)
172 {
173 $credentials= $this->get_credentials($referral);
174 if (@ldap_bind($ldap, LDAP::fix($credentials['ADMINDN']), $credentials['ADMINPASSWORD'])) {
175 $this->error = "Success";
176 $this->hascon=true;
177 $this->reconnect= true;
178 return (0);
179 } else {
180 $this->error = "Could not bind to " . $credentials['ADMINDN'];
181 return NULL;
182 }
183 }
185 function reconnect()
186 {
187 if ($this->reconnect){
188 @ldap_unbind($this->cid);
189 $this->cid = NULL;
190 }
191 }
193 function unbind()
194 {
195 @ldap_unbind($this->cid);
196 $this->cid = NULL;
197 }
199 function disconnect()
200 {
201 if($this->hascon){
202 @ldap_close($this->cid);
203 $this->hascon=false;
204 }
205 }
207 function cd($dir)
208 {
209 if ($dir == ".."){
210 $this->basedn = $this->getParentDir();
211 } else {
212 $this->basedn = LDAP::convert($dir);
213 }
214 }
216 function getParentDir($basedn = "")
217 {
218 if ($basedn==""){
219 $basedn = $this->basedn;
220 } else {
221 $basedn = LDAP::convert($basedn);
222 }
223 return(preg_replace("/[^,]*[,]*[ ]*(.*)/", "$1", $basedn));
224 }
227 function search($srp, $filter, $attrs= array())
228 {
229 if($this->hascon){
230 if ($this->reconnect) $this->connect();
232 $start = microtime(true);
233 $this->clearResult($srp);
234 $this->sr[$srp] = @ldap_search($this->cid, LDAP::fix($this->basedn), $filter, $attrs);
235 $this->error = @ldap_error($this->cid);
236 $this->resetResult($srp);
237 $this->hasres[$srp]=true;
239 /* Check if query took longer as specified in max_ldap_query_time */
240 if($this->max_ldap_query_time){
241 $diff = microtime(true) - $start;
242 if($diff > $this->max_ldap_query_time){
243 msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took %.2fs!"), $diff), WARNING_DIALOG);
244 }
245 }
247 $this->log("LDAP operation: time=".(microtime(true)-$start)." operation=search('".LDAP::fix($this->basedn)."', '$filter')");
249 // Create statistic table entry
250 stats::log('ldap', $class = get_class($this), $category = array(), $action = __FUNCTION__,
251 $amount = 1, $duration = (microtime(TRUE) - $start));
252 return($this->sr[$srp]);
253 }else{
254 $this->error = "Could not connect to LDAP server";
255 return("");
256 }
257 }
259 function ls($srp, $filter = "(objectclass=*)", $basedn = "",$attrs = array("*"))
260 {
261 if($this->hascon){
262 if ($this->reconnect) $this->connect();
264 $this->clearResult($srp);
265 if ($basedn == "")
266 $basedn = $this->basedn;
267 else
268 $basedn= LDAP::convert($basedn);
270 $start = microtime(true);
271 $this->sr[$srp] = @ldap_list($this->cid, LDAP::fix($basedn), $filter,$attrs);
272 $this->error = @ldap_error($this->cid);
273 $this->resetResult($srp);
274 $this->hasres[$srp]=true;
276 /* Check if query took longer as specified in max_ldap_query_time */
277 if($this->max_ldap_query_time){
278 $diff = microtime(true) - $start;
279 if($diff > $this->max_ldap_query_time){
280 msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took %.2fs!"), $diff), WARNING_DIALOG);
281 }
282 }
284 $this->log("LDAP operation: time=".(microtime(true) - $start)." operation=ls('".LDAP::fix($basedn)."', '$filter')");
286 // Create statistic table entry
287 stats::log('ldap', $class = get_class($this), $category = array(), $action = __FUNCTION__,
288 $amount = 1, $duration = (microtime(TRUE) - $start));
290 return($this->sr[$srp]);
291 }else{
292 $this->error = "Could not connect to LDAP server";
293 return("");
294 }
295 }
297 function cat($srp, $dn,$attrs= array("*"), $filter = "(objectclass=*)")
298 {
299 if($this->hascon){
300 if ($this->reconnect) $this->connect();
302 $this->clearResult($srp);
303 $this->sr[$srp] = @ldap_read($this->cid, LDAP::fix($dn), $filter,$attrs);
304 $this->error = @ldap_error($this->cid);
305 $this->resetResult($srp);
306 $this->hasres[$srp]=true;
307 return($this->sr[$srp]);
308 }else{
309 $this->error = "Could not connect to LDAP server";
310 return("");
311 }
312 }
314 function object_match_filter($dn,$filter)
315 {
316 if($this->hascon){
317 if ($this->reconnect) $this->connect();
318 $res = @ldap_read($this->cid, LDAP::fix($dn), $filter, array("objectClass"));
319 $rv = @ldap_count_entries($this->cid, $res);
320 return($rv);
321 }else{
322 $this->error = "Could not connect to LDAP server";
323 return(FALSE);
324 }
325 }
327 function set_size_limit($size)
328 {
329 /* Ignore zero settings */
330 if ($size == 0){
331 @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, 10000000);
332 }
333 if($this->hascon){
334 @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, $size);
335 } else {
336 $this->error = "Could not connect to LDAP server";
337 }
338 }
340 function fetch($srp)
341 {
342 $att= array();
343 if($this->hascon){
344 if($this->hasres[$srp]){
345 if ($this->start[$srp] == 0)
346 {
347 if ($this->sr[$srp]){
348 $this->start[$srp] = 1;
349 $this->re[$srp]= @ldap_first_entry($this->cid, $this->sr[$srp]);
350 } else {
351 return array();
352 }
353 } else {
354 $this->re[$srp]= @ldap_next_entry($this->cid, $this->re[$srp]);
355 }
356 if ($this->re[$srp])
357 {
358 $att= @ldap_get_attributes($this->cid, $this->re[$srp]);
359 $att['dn']= trim(LDAP::convert(@ldap_get_dn($this->cid, $this->re[$srp])));
360 }
361 $this->error = @ldap_error($this->cid);
362 if (!isset($att)){
363 $att= array();
364 }
365 return($att);
366 }else{
367 $this->error = "Perform a fetch with no search";
368 return("");
369 }
370 }else{
371 $this->error = "Could not connect to LDAP server";
372 return("");
373 }
374 }
376 function resetResult($srp)
377 {
378 $this->start[$srp] = 0;
379 }
381 function clearResult($srp)
382 {
383 if($this->hasres[$srp]){
384 $this->hasres[$srp] = false;
385 @ldap_free_result($this->sr[$srp]);
386 }
387 }
389 function getDN($srp)
390 {
391 if($this->hascon){
392 if($this->hasres[$srp]){
394 if(!$this->re[$srp])
395 {
396 $this->error = "Perform a Fetch with no valid Result";
397 }
398 else
399 {
400 $rv = @ldap_get_dn($this->cid, $this->re[$srp]);
402 $this->error = @ldap_error($this->cid);
403 return(trim(LDAP::convert($rv)));
404 }
405 }else{
406 $this->error = "Perform a Fetch with no Search";
407 return("");
408 }
409 }else{
410 $this->error = "Could not connect to LDAP server";
411 return("");
412 }
413 }
415 function count($srp)
416 {
417 if($this->hascon){
418 if($this->hasres[$srp]){
419 $rv = @ldap_count_entries($this->cid, $this->sr[$srp]);
420 $this->error = @ldap_error($this->cid);
421 return($rv);
422 }else{
423 $this->error = "Perform a Fetch with no Search";
424 return("");
425 }
426 }else{
427 $this->error = "Could not connect to LDAP server";
428 return("");
429 }
430 }
432 function rm($attrs = "", $dn = "")
433 {
434 if($this->hascon){
435 if ($this->reconnect) $this->connect();
436 if ($dn == "")
437 $dn = $this->basedn;
439 $r = ldap_mod_del($this->cid, LDAP::fix($dn), $attrs);
440 $this->error = @ldap_error($this->cid);
441 return($r);
442 }else{
443 $this->error = "Could not connect to LDAP server";
444 return("");
445 }
446 }
448 function mod_add($attrs = "", $dn = "")
449 {
450 if($this->hascon){
451 if ($this->reconnect) $this->connect();
452 if ($dn == "")
453 $dn = $this->basedn;
455 $r = @ldap_mod_add($this->cid, LDAP::fix($dn), $attrs);
456 $this->error = @ldap_error($this->cid);
457 return($r);
458 }else{
459 $this->error = "Could not connect to LDAP server";
460 return("");
461 }
462 }
464 function rename($attrs, $dn = "")
465 {
466 if($this->hascon){
467 if ($this->reconnect) $this->connect();
468 if ($dn == "")
469 $dn = $this->basedn;
471 $r = @ldap_mod_replace($this->cid, LDAP::fix($dn), $attrs);
472 $this->error = @ldap_error($this->cid);
473 return($r);
474 }else{
475 $this->error = "Could not connect to LDAP server";
476 return("");
477 }
478 }
480 function rmdir($deletedn)
481 {
482 if($this->hascon){
483 if ($this->reconnect) $this->connect();
484 $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
485 $this->error = @ldap_error($this->cid);
486 return($r ? $r : 0);
487 }else{
488 $this->error = "Could not connect to LDAP server";
489 return("");
490 }
491 }
494 /*! \brief Move the given Ldap entry from $source to $dest
495 @param String $source The source dn.
496 @param String $dest The destination dn.
497 @return Boolean TRUE on success else FALSE.
498 */
499 function rename_dn($source,$dest)
500 {
501 /* Check if source and destination are the same entry */
502 if(strtolower($source) == strtolower($dest)){
503 trigger_error("Source and destination can't be the same entry.");
504 $this->error = "Source and destination can't be the same entry.";
505 return(FALSE);
506 }
508 /* Check if destination entry exists */
509 if($this->dn_exists($dest)){
510 trigger_error("Destination '$dest' already exists.");
511 $this->error = "Destination '$dest' already exists.";
512 return(FALSE);
513 }
515 /* Extract the name and the parent part out ouf source dn.
516 e.g. cn=herbert,ou=department,dc=...
517 parent => ou=department,dc=...
518 dest_rdn => cn=herbert
519 */
520 $parent = preg_replace("/^[^,]+,/","", $dest);
521 $dest_rdn = preg_replace("/,.*$/","",$dest);
523 if($this->hascon){
524 if ($this->reconnect) $this->connect();
525 $r= ldap_rename($this->cid,@LDAP::fix($source), @LDAP::fix($dest_rdn),@LDAP::fix($parent),TRUE);
526 $this->error = ldap_error($this->cid);
528 /* Check if destination dn exists, if not the
529 server may not support this operation */
530 $r &= is_resource($this->dn_exists($dest));
531 return($r);
532 }else{
533 $this->error = "Could not connect to LDAP server";
534 return(FALSE);
535 }
536 }
539 /**
540 * Function rmdir_recursive
541 *
542 * Description: Based in recursive_remove, adding two thing: full subtree remove, and delete own node.
543 * Parameters: The dn to delete
544 * GiveBack: True on sucessfull , 0 in error, and "" when we don't get a ldap conection
545 *
546 */
547 function rmdir_recursive($srp, $deletedn)
548 {
549 if($this->hascon){
550 if ($this->reconnect) $this->connect();
551 $delarray= array();
553 /* Get sorted list of dn's to delete */
554 $this->ls ($srp, "(objectClass=*)",$deletedn);
555 while ($this->fetch($srp)){
556 $deldn= $this->getDN($srp);
557 $delarray[$deldn]= strlen($deldn);
558 }
559 arsort ($delarray);
560 reset ($delarray);
562 /* Really Delete ALL dn's in subtree */
563 foreach ($delarray as $key => $value){
564 $this->rmdir_recursive($srp, $key);
565 }
567 /* Finally Delete own Node */
568 $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
569 $this->error = @ldap_error($this->cid);
570 return($r ? $r : 0);
571 }else{
572 $this->error = "Could not connect to LDAP server";
573 return("");
574 }
575 }
577 function makeReadableErrors($error,$attrs)
578 {
579 global $config;
581 if($this->success()) return("");
583 $str = "";
584 if(preg_match("/^objectClass: value #([0-9]*) invalid per syntax$/", $this->get_additional_error())){
585 $oc = preg_replace("/^objectClass: value #([0-9]*) invalid per syntax$/","\\1", $this->get_additional_error());
586 if(isset($attrs['objectClass'][$oc])){
587 $str.= " - <b>objectClass: ".$attrs['objectClass'][$oc]."</b>";
588 }
589 }
590 if($error == "Undefined attribute type"){
591 $str = " - <b>attribute: ".preg_replace("/:.*$/","",$this->get_additional_error())."</b>";
592 }
594 @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,$attrs,"Erroneous data");
596 return($str);
597 }
599 function modify($attrs)
600 {
601 if(count($attrs) == 0){
602 return (0);
603 }
604 if($this->hascon){
605 $start = microtime(TRUE);
606 if ($this->reconnect) $this->connect();
607 $r = @ldap_modify($this->cid, LDAP::fix($this->basedn), $attrs);
608 $this->error = @ldap_error($this->cid);
609 if(!$this->success()){
610 $this->error.= $this->makeReadableErrors($this->error,$attrs);
611 }
613 // Create statistic table entry
614 stats::log('ldap', $class = get_class($this), $category = array(), $action = __FUNCTION__,
615 $amount = 1, $duration = (microtime(TRUE) - $start));
616 return($r ? $r : 0);
617 }else{
618 $this->error = "Could not connect to LDAP server";
619 return("");
620 }
621 }
623 function add($attrs)
624 {
625 if($this->hascon){
626 $start = microtime(TRUE);
627 if ($this->reconnect) $this->connect();
628 $r = @ldap_add($this->cid, LDAP::fix($this->basedn), $attrs);
629 $this->error = @ldap_error($this->cid);
630 if(!$this->success()){
631 $this->error.= $this->makeReadableErrors($this->error,$attrs);
632 }
634 // Create statistic table entry
635 stats::log('ldap', $class = get_class($this), $category = array(), $action = __FUNCTION__,
636 $amount = 1, $duration = (microtime(TRUE) - $start));
638 return($r ? $r : 0);
639 }else{
640 $this->error = "Could not connect to LDAP server";
641 return("");
642 }
643 }
645 function create_missing_trees($srp, $target)
646 {
647 global $config;
649 $real_path= substr($target, 0, strlen($target) - strlen($this->basedn) -1 );
651 if ($target == $this->basedn){
652 $l= array("dummy");
653 } else {
654 $l= array_reverse(gosa_ldap_explode_dn($real_path));
655 }
656 unset($l['count']);
657 $cdn= $this->basedn;
658 $tag= "";
660 /* Load schema if available... */
661 $classes= $this->get_objectclasses();
663 foreach ($l as $part){
664 if ($part != "dummy"){
665 $cdn= "$part,$cdn";
666 }
668 /* Ignore referrals */
669 $found= false;
670 foreach($this->referrals as $ref){
671 $base= preg_replace('!^[^:]+://[^/]+/([^?]+).*$!', '\\1', $ref['URI']);
672 if ($base == $cdn){
673 $found= true;
674 break;
675 }
676 }
677 if ($found){
678 continue;
679 }
681 $this->cat ($srp, $cdn);
682 $attrs= $this->fetch($srp);
684 /* Create missing entry? */
685 if (count ($attrs)){
687 /* Catch the tag - if present */
688 if (isset($attrs['gosaUnitTag'][0])){
689 $tag= $attrs['gosaUnitTag'][0];
690 }
692 } else {
693 $type= preg_replace('/^([^=]+)=.*$/', '\\1', $cdn);
694 $param= LDAP::fix(preg_replace('/^[^=]+=([^,]+).*$/', '\\1', $cdn));
695 $param=preg_replace(array('/\\\\,/','/\\\\"/'),array(',','"'),$param);
697 $na= array();
699 /* Automatic or traditional? */
700 if(count($classes)){
702 /* Get name of first matching objectClass */
703 $ocname= "";
704 foreach($classes as $class){
705 if (isset($class['MUST']) && in_array($type, $class['MUST'])){
707 /* Look for first classes that is structural... */
708 if (isset($class['STRUCTURAL'])){
709 $ocname= $class['NAME'];
710 break;
711 }
713 /* Look for classes that are auxiliary... */
714 if (isset($class['AUXILIARY'])){
715 $ocname= $class['NAME'];
716 }
717 }
718 }
720 /* Bail out, if we've nothing to do... */
721 if ($ocname == ""){
722 msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN %s: no object class found"), bold($type)), FATAL_ERROR_DIALOG);
723 exit();
724 }
726 /* Assemble_entry */
727 if ($tag != ""){
728 $na['objectClass']= array($ocname, "gosaAdministrativeUnitTag");
729 $na["gosaUnitTag"]= $tag;
730 } else {
731 $na['objectClass']= array($ocname);
732 }
733 if (isset($classes[$ocname]['AUXILIARY'])){
734 $na['objectClass'][]= $classes[$ocname]['SUP'];
735 }
736 if ($type == "dc"){
737 /* This is bad actually, but - tell me a better way? */
738 $na['objectClass'][]= 'locality';
739 }
740 $na[$type]= $param;
742 // Fill in MUST values - but do not overwrite existing ones.
743 if (is_array($classes[$ocname]['MUST'])){
744 foreach($classes[$ocname]['MUST'] as $attr){
745 if(isset($na[$attr]) && !empty($na[$attr])) continue;
746 $na[$attr]= "filled";
747 }
748 }
750 } else {
752 /* Use alternative add... */
753 switch ($type){
754 case 'ou':
755 if ($tag != ""){
756 $na["objectClass"]= array("organizationalUnit", "gosaAdministrativeUnitTag");
757 $na["gosaUnitTag"]= $tag;
758 } else {
759 $na["objectClass"]= "organizationalUnit";
760 }
761 $na["ou"]= $param;
762 break;
763 case 'dc':
764 if ($tag != ""){
765 $na["objectClass"]= array("dcObject", "top", "locality", "gosaAdministrativeUnitTag");
766 $na["gosaUnitTag"]= $tag;
767 } else {
768 $na["objectClass"]= array("dcObject", "top", "locality");
769 }
770 $na["dc"]= $param;
771 break;
772 default:
773 msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN %s: not supported"), bold($type)), FATAL_ERROR_DIALOG);
774 exit();
775 }
777 }
778 $this->cd($cdn);
779 $this->add($na);
781 if (!$this->success()){
783 print_a(array($cdn,$na));
785 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $cdn, LDAP_ADD, get_class()));
786 return FALSE;
787 }
788 }
789 }
791 return TRUE;
792 }
795 function recursive_remove($srp)
796 {
797 $delarray= array();
799 /* Get sorted list of dn's to delete */
800 $this->search ($srp, "(objectClass=*)");
801 while ($this->fetch($srp)){
802 $deldn= $this->getDN($srp);
803 $delarray[$deldn]= strlen($deldn);
804 }
805 arsort ($delarray);
806 reset ($delarray);
808 /* Delete all dn's in subtree */
809 foreach ($delarray as $key => $value){
810 $this->rmdir($key);
811 }
812 }
815 function get_attribute($dn, $name,$r_array=0)
816 {
817 $data= "";
818 if ($this->reconnect) $this->connect();
819 $sr= @ldap_read($this->cid, LDAP::fix($dn), "objectClass=*", array("$name"));
821 /* fill data from LDAP */
822 if ($sr) {
823 $ei= @ldap_first_entry($this->cid, $sr);
824 if ($ei) {
825 if ($info= @ldap_get_values_len($this->cid, $ei, "$name")){
826 $data= $info[0];
827 }
828 }
829 }
830 if($r_array==0) {
831 return ($data);
832 } else {
833 return ($info);
834 }
835 }
839 function get_additional_error()
840 {
841 $error= "";
842 @ldap_get_option ($this->cid, LDAP_OPT_ERROR_STRING, $error);
843 return ($error);
844 }
847 function success()
848 {
849 return (preg_match('/Success/i', $this->error));
850 }
853 function get_error()
854 {
855 if ($this->error == 'Success'){
856 return $this->error;
857 } else {
858 $adderror= $this->get_additional_error();
859 if ($adderror != ""){
860 $error= $this->error." (".$this->get_additional_error().", ".sprintf(_("while operating on %s using LDAP server %s"), bold($this->basedn), bold($this->hostname)).")";
861 } else {
862 $error= $this->error." (".sprintf(_("while operating on LDAP server %s"), bold($this->hostname)).")";
863 }
864 return $error;
865 }
866 }
868 function get_credentials($url, $referrals= NULL)
869 {
870 $ret= array();
871 $url= preg_replace('!\?\?.*$!', '', $url);
872 $server= preg_replace('!^([^:]+://[^/]+)/.*$!', '\\1', $url);
874 if ($referrals === NULL){
875 $referrals= $this->referrals;
876 }
878 if (isset($referrals[$server])){
879 return ($referrals[$server]);
880 } else {
881 $ret['ADMINDN']= LDAP::fix($this->binddn);
882 $ret['ADMINPASSWORD']= $this->bindpw;
883 }
885 return ($ret);
886 }
889 /*! \brief Generates an ldif for all entries matching the filter settings, scope and limit.
890 * @param $dn The entry to export.
891 * @param $filter Limit the exported object to those maching this filter.
892 * @param $scope 'base', 'sub' .. see manpage for 'ldapmodify' for details.
893 * @param $limit Limits the result.
894 */
895 function generateLdif ($dn, $filter= "(objectClass=*)", $scope = 'sub', $limit=0)
896 {
897 $attrs = (count($attributes))?implode($attributes,' '):'';
899 // Ensure that limit is numeric if not skip here.
900 if(!empty($limit) && !is_numeric($limit)){
901 trigger_error(sprintf("Invalid parameter for limit '%s', a numeric value is required."), $limit);
902 return(NULL);
903 }
904 $limit = (!$limit)?'':' -z '.$limit;
906 // Check scope values
907 $scope = trim($scope);
908 if(!empty($scope) && !in_array($scope, array('base', 'one', 'sub', 'children'))){
909 trigger_error(sprintf("Invalid parameter for scope '%s', please use 'base', 'one', 'sub' or 'children'."), $scope);
910 return(NULL);
911 }
912 $scope = (!empty($scope))?' -s '.$scope: '';
914 // Prepare parameters to be valid for shell execution
915 $dn = escapeshellarg($dn);
916 $pwd = $this->bindpw;
917 $host = escapeshellarg($this->hostname);
918 $admin = escapeshellarg($this->binddn);
919 $filter = escapeshellarg($filter);
920 $cmd = "ldapsearch -x -LLLL -D {$admin} {$filter} {$limit} {$scope} -H {$host} -b {$dn} -W ";
922 // Create list of process pipes
923 $descriptorspec = array(
924 0 => array("pipe", "r"), // stdin
925 1 => array("pipe", "w"), // stdout
926 2 => array("pipe", "w")); // stderr
928 // Try to open the process
929 $process = proc_open($cmd, $descriptorspec, $pipes);
930 if (is_resource($process)) {
932 // Write the password to stdin
933 fwrite($pipes[0], $pwd);
934 fclose($pipes[0]);
936 // Get results from stdout and stderr
937 $res = stream_get_contents($pipes[1]);
938 $err = stream_get_contents($pipes[2]);
939 fclose($pipes[1]);
941 // Close the process and check its return value
942 if(proc_close($process) != 0){
943 trigger_error($err);
944 }
945 }
946 return($res);
947 }
950 function gen_xls ($srp, $dn, $filter= "(objectClass=*)", $attributes= array('*'), $recursive= TRUE,$r_array=0)
951 {
952 $display= array();
954 $this->cd($dn);
955 $this->search($srp, "$filter");
957 $i=0;
958 while ($attrs= $this->fetch($srp)){
959 $j=0;
961 foreach ($attributes as $at){
962 $display[$i][$j]= $this->get_attribute($attrs['dn'], $at,$r_array);
963 $j++;
964 }
966 $i++;
967 }
969 return ($display);
970 }
973 function dn_exists($dn)
974 {
975 return @ldap_list($this->cid, LDAP::fix($dn), "(objectClass=*)", array("objectClass"));
976 }
980 /* This funktion imports ldifs
982 If DeleteOldEntries is true, the destination entry will be deleted first.
983 If JustModify is true the destination entry will only be touched by the attributes specified in the ldif.
984 if JustMofify id false the destination dn will be overwritten by the new ldif.
985 */
987 function import_complete_ldif($srp, $str_attr,$error,$JustModify,$DeleteOldEntries)
988 {
989 if($this->reconnect) $this->connect();
991 /* First we have to split the string into empty lines.
992 An empty line indicates an new Entry */
993 $entries = preg_split("/\n/",$str_attr);
995 $data = "";
996 $cnt = 0;
997 $current_line = 0;
999 /* FIX ldif */
1000 $last = "";
1001 $tmp = "";
1002 $i = 0;
1003 foreach($entries as $entry){
1004 if(preg_match("/^ /",$entry)){
1005 $tmp[$i] .= trim($entry);
1006 }else{
1007 $i ++;
1008 $tmp[$i] = trim($entry);
1009 }
1010 }
1012 /* Every single line ... */
1013 foreach($tmp as $entry) {
1014 $current_line ++;
1016 /* Removing Spaces to ..
1017 .. test if a new entry begins */
1018 $tmp = str_replace(" ","",$data );
1020 /* .. prevent empty lines in an entry */
1021 $tmp2 = str_replace(" ","",$entry);
1023 /* If the Block ends (Empty Line) */
1024 if((empty($entry))&&(!empty($tmp))) {
1025 /* Add collected lines as a complete block */
1026 $all[$cnt] = $data;
1027 $cnt ++;
1028 $data ="";
1029 } else {
1031 /* Append lines ... */
1032 if(!empty($tmp2)) {
1033 /* check if we need base64_decode for this line */
1034 if(strstr($tmp2, "::") !== false)
1035 {
1036 $encoded = explode("::",$entry);
1037 $attr = trim($encoded[0]);
1038 $value = base64_decode(trim($encoded[1]));
1039 /* Add linenumber */
1040 $data .= $current_line."#".base64_encode($attr.":".$value)."\n";
1041 }
1042 else
1043 {
1044 /* Add Linenumber */
1045 $data .= $current_line."#".base64_encode($entry)."\n";
1046 }
1047 }
1048 }
1049 }
1051 /* The Data we collected is not in the array all[];
1052 For example the Data is stored like this..
1054 all[0] = "1#dn : .... \n
1055 2#ObjectType: person \n ...."
1057 Now we check every insertblock and try to insert */
1058 foreach ( $all as $single) {
1059 $lineone = preg_split("/\n/",$single);
1060 $ndn = explode("#", $lineone[0]);
1061 $line = base64_decode($ndn[1]);
1063 $dnn = explode (":",$line,2);
1064 $current_line = $ndn[0];
1065 $dn = $dnn[0];
1066 $value = $dnn[1];
1068 /* Every block must begin with a dn */
1069 if($dn != "dn") {
1070 $error= sprintf(_("Invalid DN %s: block to be imported should start with 'dn: ...' in line %s"), bold($line), bold($current_line));
1071 return -2;
1072 }
1074 /* Should we use Modify instead of Add */
1075 $usemodify= false;
1077 /* Delete before insert */
1078 $usermdir= false;
1080 /* The dn address already exists, Don't delete destination entry, overwrite it */
1081 if (($this->dn_exists($value))&&((!$JustModify)&&(!$DeleteOldEntries))) {
1083 $usermdir = $usemodify = false;
1085 /* Delete old entry first, then add new */
1086 } elseif(($this->dn_exists($value))&&($DeleteOldEntries)){
1088 /* Delete first, then add */
1089 $usermdir = true;
1091 } elseif(($this->dn_exists($value))&&($JustModify)) {
1093 /* Modify instead of Add */
1094 $usemodify = true;
1095 }
1097 /* If we can't Import, return with a file error */
1098 if(!$this->import_single_entry($srp, $single,$usemodify,$usermdir) ) {
1099 $error= sprintf(_("Error while importing DN %s: please check LDIF from line %s on!"), bold($line),
1100 $current_line);
1101 return UNKNOWN_TOKEN_IN_LDIF_FILE; }
1102 }
1104 return (INSERT_OK);
1105 }
1108 /* Imports a single entry
1109 If $delete is true; The old entry will be deleted if it exists.
1110 if $modify is true; All variables that are not touched by the new ldif will be kept.
1111 if $modify is false; The new ldif overwrites the old entry, and all untouched attributes get lost.
1112 */
1113 function import_single_entry($srp, $str_attr,$modify,$delete)
1114 {
1115 global $config;
1117 if(!$config){
1118 trigger_error("Can't import ldif, can't read config object.");
1119 }
1122 if($this->reconnect) $this->connect();
1124 $ret = false;
1125 $rows= preg_split("/\n/",$str_attr);
1126 $data= false;
1128 foreach($rows as $row) {
1130 /* Check if we use Linenumbers (when import_complete_ldif is called we use
1131 Linenumbers) Linenumbers are use like this 123#attribute : value */
1132 if(!empty($row)) {
1133 if(strpos($row,"#")!=FALSE) {
1135 /* We are using line numbers
1136 Because there is a # before a : */
1137 $tmp1= explode("#",$row);
1138 $current_line= $tmp1[0];
1139 $row= base64_decode($tmp1[1]);
1140 }
1142 /* Split the line into attribute and value */
1143 $attr = explode(":", $row,2);
1144 $attr[0]= trim($attr[0]); /* attribute */
1145 $attr[1]= $attr[1]; /* value */
1147 /* Check :: was used to indicate base64_encoded strings */
1148 if($attr[1][0] == ":"){
1149 $attr[1]=trim(preg_replace("/^:/","",$attr[1]));
1150 $attr[1]=base64_decode($attr[1]);
1151 }
1153 $attr[1] = trim($attr[1]);
1155 /* Check for attributes that are used more than once */
1156 if(!isset($data[$attr[0]])) {
1157 $data[$attr[0]]=$attr[1];
1158 } else {
1159 $tmp = $data[$attr[0]];
1161 if(!is_array($tmp)) {
1162 $new[0]=$tmp;
1163 $new[1]=$attr[1];
1164 $datas[$attr[0]]['count']=1;
1165 $data[$attr[0]]=$new;
1166 } else {
1167 $cnt = $datas[$attr[0]]['count'];
1168 $cnt ++;
1169 $data[$attr[0]][$cnt]=$attr[1];
1170 $datas[$attr[0]]['count'] = $cnt;
1171 }
1172 }
1173 }
1174 }
1176 /* If dn is an index of data, we should try to insert the data */
1177 if(isset($data['dn'])) {
1179 /* Fix dn */
1180 $tmp = gosa_ldap_explode_dn($data['dn']);
1181 unset($tmp['count']);
1182 $newdn ="";
1183 foreach($tmp as $tm){
1184 $newdn.= trim($tm).",";
1185 }
1186 $newdn = preg_replace("/,$/","",$newdn);
1187 $data['dn'] = $newdn;
1189 /* Creating Entry */
1190 $this->cd($data['dn']);
1192 /* Delete existing entry */
1193 if($delete){
1194 $this->rmdir_recursive($srp, $data['dn']);
1195 }
1197 /* Create missing trees */
1198 $this->cd ($this->basedn);
1199 $this->cd($config->current['BASE']);
1200 $this->create_missing_trees($srp, preg_replace("/^[^,]+,/","",$data['dn']));
1201 $this->cd($data['dn']);
1203 $dn = $data['dn'];
1204 unset($data['dn']);
1206 if(!$modify){
1208 $this->cat($srp, $dn);
1209 if($this->count($srp)){
1211 /* The destination entry exists, overwrite it with the new entry */
1212 $attrs = $this->fetch($srp);
1213 foreach($attrs as $name => $value ){
1214 if(!is_numeric($name)){
1215 if(in_array($name,array("dn","count"))) continue;
1216 if(!isset($data[$name])){
1217 $data[$name] = array();
1218 }
1219 }
1220 }
1221 $ret = $this->modify($data);
1223 }else{
1225 /* The destination entry doesn't exists, create it */
1226 $ret = $this->add($data);
1227 }
1229 } else {
1231 /* Keep all vars that aren't touched by this ldif */
1232 $ret = $this->modify($data);
1233 }
1234 }
1236 if (!$this->success()){
1237 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $dn, "", get_class()));
1238 }
1240 return($ret);
1241 }
1244 function importcsv($str)
1245 {
1246 $lines = preg_split("/\n/",$str);
1247 foreach($lines as $line)
1248 {
1249 /* continue if theres a comment */
1250 if(substr(trim($line),0,1)=="#"){
1251 continue;
1252 }
1254 $line= str_replace ("\t\t","\t",$line);
1255 $line= str_replace ("\t" ,"," ,$line);
1256 echo $line;
1258 $cells = explode(",",$line ) ;
1259 $linet= str_replace ("\t\t",",",$line);
1260 $cells = preg_split("/\t/",$line);
1261 $count = count($cells);
1262 }
1264 }
1266 function get_objectclasses( $force_reload = FALSE)
1267 {
1268 $objectclasses = array();
1269 global $config;
1271 /* Return the cached results. */
1272 if(class_available('session') && session::global_is_set("LDAP_CACHE::get_objectclasses") && !$force_reload){
1273 $objectclasses = session::global_get("LDAP_CACHE::get_objectclasses");
1274 return($objectclasses);
1275 }
1277 # Get base to look for schema
1278 $sr = @ldap_read ($this->cid, "", "objectClass=*", array("subschemaSubentry"));
1279 $attr = @ldap_get_entries($this->cid,$sr);
1280 if (!isset($attr[0]['subschemasubentry'][0])){
1281 return array();
1282 }
1284 /* Get list of objectclasses and fill array */
1285 $nb= $attr[0]['subschemasubentry'][0];
1286 $objectclasses= array();
1287 $sr= ldap_read ($this->cid, $nb, "objectClass=*", array("objectclasses"));
1288 $attrs= ldap_get_entries($this->cid,$sr);
1289 if (!isset($attrs[0])){
1290 return array();
1291 }
1292 foreach ($attrs[0]['objectclasses'] as $val){
1293 if (preg_match('/^[0-9]+$/', $val)){
1294 continue;
1295 }
1296 $name= "OID";
1297 $pattern= explode(' ', $val);
1298 $ocname= preg_replace("/^.* NAME\s+\(*\s*'([^']+)'\s*\)*.*$/", '\\1', $val);
1299 $objectclasses[$ocname]= array();
1301 foreach($pattern as $chunk){
1302 switch($chunk){
1304 case '(':
1305 $value= "";
1306 break;
1308 case ')': if ($name != ""){
1309 $v = $this->value2container($value);
1310 if(in_array($name, array('MUST', 'MAY')) && !is_array($v)){
1311 $v = array($v);
1312 }
1313 $objectclasses[$ocname][$name]= $v;
1314 }
1315 $name= "";
1316 $value= "";
1317 break;
1319 case 'NAME':
1320 case 'DESC':
1321 case 'SUP':
1322 case 'STRUCTURAL':
1323 case 'ABSTRACT':
1324 case 'AUXILIARY':
1325 case 'MUST':
1326 case 'MAY':
1327 if ($name != ""){
1328 $v = $this->value2container($value);
1329 if(in_array($name, array('MUST', 'MAY')) && !is_array($v)){
1330 $v = array($v);
1331 }
1332 $objectclasses[$ocname][$name]= $v;
1333 }
1334 $name= $chunk;
1335 $value= "";
1336 break;
1338 default: $value.= $chunk." ";
1339 }
1340 }
1342 }
1343 if(class_available("session")){
1344 session::global_set("LDAP_CACHE::get_objectclasses",$objectclasses);
1345 }
1347 return $objectclasses;
1348 }
1351 function value2container($value)
1352 {
1353 /* Set emtpy values to "true" only */
1354 if (preg_match('/^\s*$/', $value)){
1355 return true;
1356 }
1358 /* Remove ' and " if needed */
1359 $value= preg_replace('/^[\'"]/', '', $value);
1360 $value= preg_replace('/[\'"] *$/', '', $value);
1362 /* Convert to array if $ is inside... */
1363 if (preg_match('/\$/', $value)){
1364 $container= preg_split('/\s*\$\s*/', $value);
1365 } else {
1366 $container= chop($value);
1367 }
1369 return ($container);
1370 }
1373 function log($string)
1374 {
1375 if (session::global_is_set('config')){
1376 $cfg = session::global_get('config');
1377 if (isset($cfg->current['LDAPSTATS']) && preg_match('/true/i', $cfg->current['LDAPSTATS'])){
1378 syslog (LOG_INFO, $string);
1379 }
1380 }
1381 }
1383 /* added by Guido Serra aka Zeph <zeph@purotesto.it> */
1384 function getCn($dn){
1385 $simple= explode(",", $dn);
1387 foreach($simple as $piece) {
1388 $partial= explode("=", $piece);
1390 if($partial[0] == "cn"){
1391 return $partial[1];
1392 }
1393 }
1394 }
1397 function get_naming_contexts($server, $admin= "", $password= "")
1398 {
1399 /* Build LDAP connection */
1400 $ds= ldap_connect ($server);
1401 if (!$ds) {
1402 die ("Can't bind to LDAP. No check possible!");
1403 }
1404 ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1405 $r= ldap_bind ($ds, $admin, $password);
1407 /* Get base to look for naming contexts */
1408 $sr = @ldap_read ($ds, "", "objectClass=*", array("+"));
1409 $attr= @ldap_get_entries($ds,$sr);
1411 return ($attr[0]['namingcontexts']);
1412 }
1415 function get_root_dse($server, $admin= "", $password= "")
1416 {
1417 /* Build LDAP connection */
1418 $ds= ldap_connect ($server);
1419 if (!$ds) {
1420 die ("Can't bind to LDAP. No check possible!");
1421 }
1422 ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1423 $r= ldap_bind ($ds, $admin, $password);
1425 /* Get base to look for naming contexts */
1426 $sr = @ldap_read ($ds, "", "objectClass=*", array("+"));
1427 $attr= @ldap_get_entries($ds,$sr);
1429 /* Return empty array, if nothing was set */
1430 if (!isset($attr[0])){
1431 return array();
1432 }
1434 /* Rework array... */
1435 $result= array();
1436 for ($i= 0; $i<$attr[0]['count']; $i++){
1437 $result[$attr[0][$i]]= $attr[0][$attr[0][$i]];
1438 unset($result[$attr[0][$i]]['count']);
1439 }
1441 return ($result);
1442 }
1444 }
1445 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1446 ?>