Code

Updated ldap special character handling
[gosa.git] / gosa-core / include / class_ldap.inc
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){
119     
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   }
226   
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;
238    
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);
269   
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]);
401         
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();
552         
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       }
566       
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)){
686       
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);
780     
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   }
836  
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
927     
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   }
977   
980   /*  This funktion imports ldifs 
981         
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 ...."
1056        
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;
1079     
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)) {
1092         
1093         /* Modify instead of Add */
1094         $usemodify = true;
1095       }
1096      
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     }
1120   
1122     if($this->reconnect) $this->connect();
1124     $ret = false;
1125     $rows= preg_split("/\n/",$str_attr);
1126     $data= false;
1128     foreach($rows as $row) {
1129       
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;
1188    
1189       /* Creating Entry */
1190       $this->cd($data['dn']);
1192       /* Delete existing entry */
1193       if($delete){
1194         $this->rmdir_recursive($srp, $data['dn']);
1195       }
1196      
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']);
1205       
1206       if(!$modify){
1208         $this->cat($srp, $dn);
1209         if($this->count($srp)){
1210         
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);
1222     
1223         }else{
1224     
1225           /* The destination entry doesn't exists, create it */
1226           $ret = $this->add($data);
1227         }
1229       } else {
1230         
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   }
1243   
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   }
1265   
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     }
1276         
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           }
1283         
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);
1428    
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   }
1445 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1446 ?>