Code

Added stats actions to management and class ldap
[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{
33   var $hascon   =false;
34   var $reconnect=false;
35   var $tls      = false;
36   var $cid;
37   var $hasres   = array();
38   var $sr       = array();
39   var $re       = array();
40   var $basedn   ="";
41   var $start    = array(); // 0 if we are fetching the first entry, otherwise 1
42   var $error    = ""; // Any error messages to be returned can be put here
43   var $srp      = 0;
44   var $objectClasses = array(); // Information read from slapd.oc.conf
45   var $binddn   = "";
46   var $bindpw   = "";
47   var $hostname = "";
48   var $follow_referral = FALSE;
49   var $referrals= array();
50   var $max_ldap_query_time = 0;   // 0, empty or negative values will disable this check 
52   function LDAP($binddn,$bindpw, $hostname, $follow_referral= FALSE, $tls= FALSE)
53   {
54     global $config;
55     $this->follow_referral= $follow_referral;
56     $this->tls=$tls;
57     $this->binddn=LDAP::convert($binddn);
59     $this->bindpw=$bindpw;
60     $this->hostname=$hostname;
62     /* Check if MAX_LDAP_QUERY_TIME is defined */ 
63     if(is_object($config) && $config->get_cfg_value("core","ldapMaxQueryTime") != ""){
64       $str = $config->get_cfg_value("core","ldapMaxQueryTime");
65       $this->max_ldap_query_time = (float)($str);
66     }
68     $this->connect();
69   }
72   function getSearchResource()
73   {
74     $this->sr[$this->srp]= NULL;
75     $this->start[$this->srp]= 0;
76     $this->hasres[$this->srp]= false;
77     return $this->srp++;
78   }
81   /* Function to replace all problematic characters inside a DN by \001XX, where
82      \001 is decoded to chr(1) [ctrl+a]. It is not impossible, but very unlikely
83      that this character is inside a DN.
85      Currently used codes:
86      ,   => CO
87      \2C => CO
88      (   => OB
89      )   => CB
90      /   => SL                                                                  
91      \22 => DQ                                                                  */
92   static function convert($dn)
93   {
94     if (SPECIALS_OVERRIDE == TRUE){
95       $tmp= preg_replace(array("/\\\\,/", "/\\\\2C/", "/\(/", "/\)/", "/\//", "/\\\\22/", '/\\\\"/'),
96           array("\001CO", "\001CO", "\001OB", "\001CB", "\001SL", "\001DQ", "\001DQ"),
97           $dn);
98       return (preg_replace('/,\s+/', ',', $tmp));
99     } else {
100       return ($dn);
101     }
102   }
105   /* Function to fix all problematic characters inside a DN by replacing \001XX
106      codes to their original values. See "convert" for mor information. 
107      ',' characters are always expanded to \, (not \2C), since all tested LDAP
108      servers seem to take it the correct way.                                  */
109   static function fix($dn)
110   {
111     if (SPECIALS_OVERRIDE == TRUE){
112       return (preg_replace(array("/\001CO/", "/\001OB/", "/\001CB/", "/\001SL/", "/\001DQ/"),
113             array("\,", "(", ")", "/", '\"'),
114             $dn));
115     } else {
116       return ($dn);
117     }
118   }
120   /* Function to fix problematic characters in DN's that are used for search
121      requests. I.e. member=....                                               */
122   static function prepare4filter($dn)
123   {
124     $fixed= normalizeLdap(str_replace('\\\\', '\\\\\\', LDAP::fix($dn)));
125     return str_replace('\\,', '\\\\,', $fixed);
126   }
129   function connect()
130   {
131     $this->hascon=false;
132     $this->reconnect=false;
133     if ($this->cid= @ldap_connect($this->hostname)) {
134       @ldap_set_option($this->cid, LDAP_OPT_PROTOCOL_VERSION, 3);
135       if (function_exists("ldap_set_rebind_proc") && $this->follow_referral) {
136         @ldap_set_option($this->cid, LDAP_OPT_REFERRALS, 1);
137         @ldap_set_rebind_proc($this->cid, array(&$this, "rebind"));
138       }
139       if (function_exists("ldap_start_tls") && $this->tls){
140         @ldap_start_tls($this->cid);
141       }
143       $this->error = "No Error";
144       if ($bid = @ldap_bind($this->cid, LDAP::fix($this->binddn), $this->bindpw)) {
145         $this->error = "Success";
146         $this->hascon=true;
147       } else {
148         if ($this->reconnect){
149           if ($this->error != "Success"){
150             $this->error = "Could not rebind to " . $this->binddn;
151           }
152         } else {
153           $this->error = "Could not bind to " . $this->binddn;
154         }
155       }
156     } else {
157       $this->error = "Could not connect to LDAP server";
158     }
159   }
161   function rebind($ldap, $referral)
162   {
163     $credentials= $this->get_credentials($referral);
164     if (@ldap_bind($ldap, LDAP::fix($credentials['ADMINDN']), $credentials['ADMINPASSWORD'])) {
165       $this->error = "Success";
166       $this->hascon=true;
167       $this->reconnect= true;
168       return (0);
169     } else {
170       $this->error = "Could not bind to " . $credentials['ADMINDN'];
171       return NULL;
172     }
173   }
175   function reconnect()
176   {
177     if ($this->reconnect){
178       @ldap_unbind($this->cid);
179       $this->cid = NULL;
180     }
181   }
183   function unbind()
184   {
185     @ldap_unbind($this->cid);
186     $this->cid = NULL;
187   }
189   function disconnect()
190   {
191     if($this->hascon){
192       @ldap_close($this->cid);
193       $this->hascon=false;
194     }
195   }
197   function cd($dir)
198   {
199     if ($dir == ".."){
200       $this->basedn = $this->getParentDir();
201     } else {
202       $this->basedn = LDAP::convert($dir);
203     }
204   }
206   function getParentDir($basedn = "")
207   {
208     if ($basedn==""){
209       $basedn = $this->basedn;
210     } else {
211       $basedn = LDAP::convert($basedn);
212     }
213     return(preg_replace("/[^,]*[,]*[ ]*(.*)/", "$1", $basedn));
214   }
216   
217   function search($srp, $filter, $attrs= array())
218   {
219     if($this->hascon){
220       if ($this->reconnect) $this->connect();
222       $start = microtime(true);
223       $this->clearResult($srp);
224       $this->sr[$srp] = @ldap_search($this->cid, LDAP::fix($this->basedn), $filter, $attrs);
225       $this->error = @ldap_error($this->cid);
226       $this->resetResult($srp);
227       $this->hasres[$srp]=true;
228    
229       /* Check if query took longer as specified in max_ldap_query_time */
230       if($this->max_ldap_query_time){
231         $diff = microtime(true) - $start;
232         if($diff > $this->max_ldap_query_time){
233           msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took %.2fs!"), $diff), WARNING_DIALOG);
234         }
235       }
237       $this->log("LDAP operation: time=".(microtime(true)-$start)." operation=search('".LDAP::fix($this->basedn)."', '$filter')");
238       stats::log('ldap', $class = get_class($this), $action = __FUNCTION__, $amount = 1, $duration = (microtime(TRUE) - $start));
239       return($this->sr[$srp]);
240     }else{
241       $this->error = "Could not connect to LDAP server";
242       return("");
243     }
244   }
246   function ls($srp, $filter = "(objectclass=*)", $basedn = "",$attrs = array("*"))
247   {
248     if($this->hascon){
249       if ($this->reconnect) $this->connect();
251       $this->clearResult($srp);
252       if ($basedn == "")
253         $basedn = $this->basedn;
254       else
255         $basedn= LDAP::convert($basedn);
256   
257       $start = microtime(true);
258       $this->sr[$srp] = @ldap_list($this->cid, LDAP::fix($basedn), $filter,$attrs);
259       $this->error = @ldap_error($this->cid);
260       $this->resetResult($srp);
261       $this->hasres[$srp]=true;
263        /* Check if query took longer as specified in max_ldap_query_time */
264       if($this->max_ldap_query_time){
265         $diff = microtime(true) - $start;
266         if($diff > $this->max_ldap_query_time){
267           msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took %.2fs!"), $diff), WARNING_DIALOG);
268         }
269       }
271       $this->log("LDAP operation: time=".(microtime(true) - $start)." operation=ls('".LDAP::fix($basedn)."', '$filter')");
272       stats::log('ldap', $class = get_class($this), $action = __FUNCTION__, $amount = 1, $duration = (microtime(TRUE) - $start));
274       return($this->sr[$srp]);
275     }else{
276       $this->error = "Could not connect to LDAP server";
277       return("");
278     }
279   }
281   function cat($srp, $dn,$attrs= array("*"), $filter = "(objectclass=*)")
282   {
283     if($this->hascon){
284       if ($this->reconnect) $this->connect();
286       $this->clearResult($srp);
287       $this->sr[$srp] = @ldap_read($this->cid, LDAP::fix($dn), $filter,$attrs);
288       $this->error = @ldap_error($this->cid);
289       $this->resetResult($srp);
290       $this->hasres[$srp]=true;
291       return($this->sr[$srp]);
292     }else{
293       $this->error = "Could not connect to LDAP server";
294       return("");
295     }
296   }
298   function object_match_filter($dn,$filter)
299   {
300     if($this->hascon){
301       if ($this->reconnect) $this->connect();
302       $res =  @ldap_read($this->cid, LDAP::fix($dn), $filter, array("objectClass"));
303       $rv =   @ldap_count_entries($this->cid, $res);
304       return($rv);
305     }else{
306       $this->error = "Could not connect to LDAP server";
307       return(FALSE);
308     }
309   }
311   function set_size_limit($size)
312   {
313     /* Ignore zero settings */
314     if ($size == 0){
315       @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, 10000000);
316     }
317     if($this->hascon){
318       @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, $size);
319     } else {
320       $this->error = "Could not connect to LDAP server";
321     }
322   }
324   function fetch($srp)
325   {
326     $att= array();
327     if($this->hascon){
328       if($this->hasres[$srp]){
329         if ($this->start[$srp] == 0)
330         {
331           if ($this->sr[$srp]){
332             $this->start[$srp] = 1;
333             $this->re[$srp]= @ldap_first_entry($this->cid, $this->sr[$srp]);
334           } else {
335             return array();
336           }
337         } else {
338           $this->re[$srp]= @ldap_next_entry($this->cid, $this->re[$srp]);
339         }
340         if ($this->re[$srp])
341         {
342           $att= @ldap_get_attributes($this->cid, $this->re[$srp]);
343           $att['dn']= trim(LDAP::convert(@ldap_get_dn($this->cid, $this->re[$srp])));
344         }
345         $this->error = @ldap_error($this->cid);
346         if (!isset($att)){
347           $att= array();
348         }
349         return($att);
350       }else{
351         $this->error = "Perform a fetch with no search";
352         return("");
353       }
354     }else{
355       $this->error = "Could not connect to LDAP server";
356       return("");
357     }
358   }
360   function resetResult($srp)
361   {
362     $this->start[$srp] = 0;
363   }
365   function clearResult($srp)
366   {
367     if($this->hasres[$srp]){
368       $this->hasres[$srp] = false;
369       @ldap_free_result($this->sr[$srp]);
370     }
371   }
373   function getDN($srp)
374   {
375     if($this->hascon){
376       if($this->hasres[$srp]){
378         if(!$this->re[$srp])
379           {
380           $this->error = "Perform a Fetch with no valid Result";
381           }
382           else
383           {
384           $rv = @ldap_get_dn($this->cid, $this->re[$srp]);
385         
386           $this->error = @ldap_error($this->cid);
387           return(trim(LDAP::convert($rv)));
388            }
389       }else{
390         $this->error = "Perform a Fetch with no Search";
391         return("");
392       }
393     }else{
394       $this->error = "Could not connect to LDAP server";
395       return("");
396     }
397   }
399   function count($srp)
400   {
401     if($this->hascon){
402       if($this->hasres[$srp]){
403         $rv = @ldap_count_entries($this->cid, $this->sr[$srp]);
404         $this->error = @ldap_error($this->cid);
405         return($rv);
406       }else{
407         $this->error = "Perform a Fetch with no Search";
408         return("");
409       }
410     }else{
411       $this->error = "Could not connect to LDAP server";
412       return("");
413     }
414   }
416   function rm($attrs = "", $dn = "")
417   {
418     if($this->hascon){
419       if ($this->reconnect) $this->connect();
420       if ($dn == "")
421         $dn = $this->basedn;
423       $r = ldap_mod_del($this->cid, LDAP::fix($dn), $attrs);
424       $this->error = @ldap_error($this->cid);
425       return($r);
426     }else{
427       $this->error = "Could not connect to LDAP server";
428       return("");
429     }
430   }
432   function mod_add($attrs = "", $dn = "")
433   {
434     if($this->hascon){
435       if ($this->reconnect) $this->connect();
436       if ($dn == "")
437         $dn = $this->basedn;
439       $r = @ldap_mod_add($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 rename($attrs, $dn = "")
449   {
450     if($this->hascon){
451       if ($this->reconnect) $this->connect();
452       if ($dn == "")
453         $dn = $this->basedn;
455       $r = @ldap_mod_replace($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 rmdir($deletedn)
465   {
466     if($this->hascon){
467       if ($this->reconnect) $this->connect();
468       $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
469       $this->error = @ldap_error($this->cid);
470       return($r ? $r : 0);
471     }else{
472       $this->error = "Could not connect to LDAP server";
473       return("");
474     }
475   }
478   /*! \brief Move the given Ldap entry from $source to $dest
479       @param  String  $source The source dn.
480       @param  String  $dest   The destination dn.
481       @return Boolean TRUE on success else FALSE.
482    */
483   function rename_dn($source,$dest)
484   {
485     /* Check if source and destination are the same entry */
486     if(strtolower($source) == strtolower($dest)){
487       trigger_error("Source and destination can't be the same entry.");
488       $this->error = "Source and destination can't be the same entry.";
489       return(FALSE);
490     }
492     /* Check if destination entry exists */    
493     if($this->dn_exists($dest)){
494       trigger_error("Destination '$dest' already exists.");
495       $this->error = "Destination '$dest' already exists.";
496       return(FALSE);
497     }
499     /* Extract the name and the parent part out ouf source dn.
500         e.g.  cn=herbert,ou=department,dc=... 
501          parent   =>  ou=department,dc=...
502          dest_rdn =>  cn=herbert
503      */
504     $parent   = preg_replace("/^[^,]+,/","", $dest);
505     $dest_rdn = preg_replace("/,.*$/","",$dest);
507     if($this->hascon){
508       if ($this->reconnect) $this->connect();
509       $r= ldap_rename($this->cid,@LDAP::fix($source), @LDAP::fix($dest_rdn),@LDAP::fix($parent),TRUE); 
510       $this->error = ldap_error($this->cid);
512       /* Check if destination dn exists, if not the 
513           server may not support this operation */
514       $r &= is_resource($this->dn_exists($dest));
515       return($r);
516     }else{
517       $this->error = "Could not connect to LDAP server";
518       return(FALSE);
519     }
520   }
523   /**
524   *  Function rmdir_recursive
525   *
526   *  Description: Based in recursive_remove, adding two thing: full subtree remove, and delete own node.
527   *  Parameters:  The dn to delete
528   *  GiveBack:    True on sucessfull , 0 in error, and "" when we don't get a ldap conection
529   *
530   */
531   function rmdir_recursive($srp, $deletedn)
532   {
533     if($this->hascon){
534       if ($this->reconnect) $this->connect();
535       $delarray= array();
536         
537       /* Get sorted list of dn's to delete */
538       $this->ls ($srp, "(objectClass=*)",$deletedn);
539       while ($this->fetch($srp)){
540         $deldn= $this->getDN($srp);
541         $delarray[$deldn]= strlen($deldn);
542       }
543       arsort ($delarray);
544       reset ($delarray);
546       /* Really Delete ALL dn's in subtree */
547       foreach ($delarray as $key => $value){
548         $this->rmdir_recursive($srp, $key);
549       }
550       
551       /* Finally Delete own Node */
552       $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
553       $this->error = @ldap_error($this->cid);
554       return($r ? $r : 0);
555     }else{
556       $this->error = "Could not connect to LDAP server";
557       return("");
558     }
559   }
561   function makeReadableErrors($error,$attrs)
562   { 
563     global $config;
565     if($this->success()) return("");
567     $str = "";
568     if(preg_match("/^objectClass: value #([0-9]*) invalid per syntax$/", $this->get_additional_error())){
569       $oc = preg_replace("/^objectClass: value #([0-9]*) invalid per syntax$/","\\1", $this->get_additional_error());
570       if(isset($attrs['objectClass'][$oc])){
571         $str.= " - <b>objectClass: ".$attrs['objectClass'][$oc]."</b>";
572       }
573     }
574     if($error == "Undefined attribute type"){
575       $str = " - <b>attribute: ".preg_replace("/:.*$/","",$this->get_additional_error())."</b>";
576     } 
578     @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,$attrs,"Erroneous data");
580     return($str);
581   }
583   function modify($attrs)
584   {
585     if(count($attrs) == 0){
586       return (0);
587     }
588     if($this->hascon){
589       $start = microtime(TRUE);
590       if ($this->reconnect) $this->connect();
591       $r = @ldap_modify($this->cid, LDAP::fix($this->basedn), $attrs);
592       $this->error = @ldap_error($this->cid);
593       if(!$this->success()){
594         $this->error.= $this->makeReadableErrors($this->error,$attrs);
595       }
596       stats::log('ldap', $class = get_class($this), $action = __FUNCTION__, $amount = 1, $duration = (microtime(TRUE) - $start));
597       return($r ? $r : 0);
598     }else{
599       $this->error = "Could not connect to LDAP server";
600       return("");
601     }
602   }
604   function add($attrs)
605   {
606     if($this->hascon){
607       $start = microtime(TRUE);
608       if ($this->reconnect) $this->connect();
609       $r = @ldap_add($this->cid, LDAP::fix($this->basedn), $attrs);
610       $this->error = @ldap_error($this->cid);
611       if(!$this->success()){
612         $this->error.= $this->makeReadableErrors($this->error,$attrs);
613       }
614       stats::log('ldap', $class = get_class($this), $action = __FUNCTION__, $amount = 1, $duration = (microtime(TRUE) - $start));
615       return($r ? $r : 0);
616     }else{
617       $this->error = "Could not connect to LDAP server";
618       return("");
619     }
620   }
622   function create_missing_trees($srp, $target)
623   {
624     global $config;
626     $real_path= substr($target, 0, strlen($target) - strlen($this->basedn) -1 );
628     if ($target == $this->basedn){
629       $l= array("dummy");
630     } else {
631       $l= array_reverse(gosa_ldap_explode_dn($real_path));
632     }
633     unset($l['count']);
634     $cdn= $this->basedn;
635     $tag= "";
637     /* Load schema if available... */
638     $classes= $this->get_objectclasses();
640     foreach ($l as $part){
641       if ($part != "dummy"){
642         $cdn= "$part,$cdn";
643       }
645       /* Ignore referrals */
646       $found= false;
647       foreach($this->referrals as $ref){
648         $base= preg_replace('!^[^:]+://[^/]+/([^?]+).*$!', '\\1', $ref['URI']);
649         if ($base == $cdn){
650           $found= true;
651           break;
652         }
653       }
654       if ($found){
655         continue;
656       }
658       $this->cat ($srp, $cdn);
659       $attrs= $this->fetch($srp);
661       /* Create missing entry? */
662       if (count ($attrs)){
663       
664         /* Catch the tag - if present */
665         if (isset($attrs['gosaUnitTag'][0])){
666           $tag= $attrs['gosaUnitTag'][0];
667         }
669       } else {
670         $type= preg_replace('/^([^=]+)=.*$/', '\\1', $cdn);
671         $param= LDAP::fix(preg_replace('/^[^=]+=([^,]+).*$/', '\\1', $cdn));
672         $param=preg_replace(array('/\\\\,/','/\\\\"/'),array(',','"'),$param);
674         $na= array();
676         /* Automatic or traditional? */
677         if(count($classes)){
679           /* Get name of first matching objectClass */
680           $ocname= "";
681           foreach($classes as $class){
682             if (isset($class['MUST']) && in_array($type, $class['MUST'])){
684               /* Look for first classes that is structural... */
685               if (isset($class['STRUCTURAL'])){
686                 $ocname= $class['NAME'];
687                 break;
688               }
690               /* Look for classes that are auxiliary... */
691               if (isset($class['AUXILIARY'])){
692                 $ocname= $class['NAME'];
693               }
694             }
695           }
697           /* Bail out, if we've nothing to do... */
698           if ($ocname == ""){
699             msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN %s: no object class found"), bold($type)), FATAL_ERROR_DIALOG);
700             exit();
701           }
703           /* Assemble_entry */
704           if ($tag != ""){
705             $na['objectClass']= array($ocname, "gosaAdministrativeUnitTag");
706             $na["gosaUnitTag"]= $tag;
707           } else {
708             $na['objectClass']= array($ocname);
709           }
710           if (isset($classes[$ocname]['AUXILIARY'])){
711             $na['objectClass'][]= $classes[$ocname]['SUP'];
712           }
713           if ($type == "dc"){
714             /* This is bad actually, but - tell me a better way? */
715             $na['objectClass'][]= 'locality';
716           }
717           $na[$type]= $param;
719           // Fill in MUST values - but do not overwrite existing ones.
720           if (is_array($classes[$ocname]['MUST'])){
721             foreach($classes[$ocname]['MUST'] as $attr){
722               if(isset($na[$attr]) && !empty($na[$attr])) continue;
723               $na[$attr]= "filled";
724             }
725           }
727         } else {
729           /* Use alternative add... */
730           switch ($type){
731             case 'ou':
732               if ($tag != ""){
733                 $na["objectClass"]= array("organizationalUnit", "gosaAdministrativeUnitTag");
734                 $na["gosaUnitTag"]= $tag;
735               } else {
736                 $na["objectClass"]= "organizationalUnit";
737               }
738               $na["ou"]= $param;
739               break;
740             case 'dc':
741               if ($tag != ""){
742                 $na["objectClass"]= array("dcObject", "top", "locality", "gosaAdministrativeUnitTag");
743                 $na["gosaUnitTag"]= $tag;
744               } else {
745                 $na["objectClass"]= array("dcObject", "top", "locality");
746               }
747               $na["dc"]= $param;
748               break;
749             default:
750               msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN %s: not supported"), bold($type)), FATAL_ERROR_DIALOG);
751               exit();
752           }
754         }
755         $this->cd($cdn);
756         $this->add($na);
757     
758         if (!$this->success()){
760           print_a(array($cdn,$na));
762           msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $cdn, LDAP_ADD, get_class()));
763           return FALSE;
764         }
765       }
766     }
768     return TRUE;
769   }
772   function recursive_remove($srp)
773   {
774     $delarray= array();
776     /* Get sorted list of dn's to delete */
777     $this->search ($srp, "(objectClass=*)");
778     while ($this->fetch($srp)){
779       $deldn= $this->getDN($srp);
780       $delarray[$deldn]= strlen($deldn);
781     }
782     arsort ($delarray);
783     reset ($delarray);
785     /* Delete all dn's in subtree */
786     foreach ($delarray as $key => $value){
787       $this->rmdir($key);
788     }
789   }
792   function get_attribute($dn, $name,$r_array=0)
793   {
794     $data= "";
795     if ($this->reconnect) $this->connect();
796     $sr= @ldap_read($this->cid, LDAP::fix($dn), "objectClass=*", array("$name"));
798     /* fill data from LDAP */
799     if ($sr) {
800       $ei= @ldap_first_entry($this->cid, $sr);
801       if ($ei) {
802         if ($info= @ldap_get_values_len($this->cid, $ei, "$name")){
803           $data= $info[0];
804         }
805       }
806     }
807     if($r_array==0) {
808       return ($data);
809     } else {
810       return ($info);
811     }
812   }
813  
816   function get_additional_error()
817   {
818     $error= "";
819     @ldap_get_option ($this->cid, LDAP_OPT_ERROR_STRING, $error);
820     return ($error);
821   }
824   function success()
825   {
826     return (preg_match('/Success/i', $this->error));
827   }
830   function get_error()
831   {
832     if ($this->error == 'Success'){
833       return $this->error;
834     } else {
835       $adderror= $this->get_additional_error();
836       if ($adderror != ""){
837         $error= $this->error." (".$this->get_additional_error().", ".sprintf(_("while operating on %s using LDAP server %s"), bold($this->basedn), bold($this->hostname)).")";
838       } else {
839         $error= $this->error." (".sprintf(_("while operating on LDAP server %s"), bold($this->hostname)).")";
840       }
841       return $error;
842     }
843   }
845   function get_credentials($url, $referrals= NULL)
846   {
847     $ret= array();
848     $url= preg_replace('!\?\?.*$!', '', $url);
849     $server= preg_replace('!^([^:]+://[^/]+)/.*$!', '\\1', $url);
851     if ($referrals === NULL){
852       $referrals= $this->referrals;
853     }
855     if (isset($referrals[$server])){
856       return ($referrals[$server]);
857     } else {
858       $ret['ADMINDN']= LDAP::fix($this->binddn);
859       $ret['ADMINPASSWORD']= $this->bindpw;
860     }
862     return ($ret);
863   }
866   /*! \brief  Generates an ldif for all entries matching the filter settings, scope and limit.
867    *  @param  $dn           The entry to export.
868    *  @param  $filter       Limit the exported object to those maching this filter.
869    *  @param  $attributes   Specify the attributes to export here, empty means all.
870    *  @param  $scope        'base', 'sub' .. see manpage for 'ldapmodify' for details.
871    *  @param  $limit        Limits the result.
872    */
873   function generateLdif ($dn, $filter= "(objectClass=*)", $attributes= array(), $scope = 'sub', $limit=0)
874   {
875       $attrs  = (count($attributes))?implode($attributes,' '):'';
876       $scope = (!empty($scope))?' -s '.$scope: '';
877       $limit = (!$limit)?'':' -z '.$limit;
878       $dn = escapeshellarg($dn);
879       $admin = escapeshellarg($this->binddn);
880       $pwd = escapeshellarg($this->bindpw);
881       $filter = escapeshellarg($filter);
882       $host = escapeshellarg($this->hostname);
883       $cmd = "ldapsearch -x -LLLL -D {$admin} -w {$pwd} {$filter} {$limit} {$scope} -H {$host} -b {$dn} $attrs ";
884       ob_start();
885       passthru($cmd);
886       $res=ob_get_contents();
887       ob_end_clean();
888       return($res);
889   }
892   function gen_xls ($srp, $dn, $filter= "(objectClass=*)", $attributes= array('*'), $recursive= TRUE,$r_array=0)
893   {
894     $display= array();
896       $this->cd($dn);
897       $this->search($srp, "$filter");
899       $i=0;
900       while ($attrs= $this->fetch($srp)){
901         $j=0;
903         foreach ($attributes as $at){
904           $display[$i][$j]= $this->get_attribute($attrs['dn'], $at,$r_array);
905           $j++;
906         }
908         $i++;
909       }
911     return ($display);
912   }
915   function dn_exists($dn)
916   {
917     return @ldap_list($this->cid, LDAP::fix($dn), "(objectClass=*)", array("objectClass"));
918   }
919   
922   /*  This funktion imports ldifs 
923         
924       If DeleteOldEntries is true, the destination entry will be deleted first. 
925       If JustModify is true the destination entry will only be touched by the attributes specified in the ldif.
926       if JustMofify id false the destination dn will be overwritten by the new ldif. 
927     */
929   function import_complete_ldif($srp, $str_attr,$error,$JustModify,$DeleteOldEntries)
930   {
931     if($this->reconnect) $this->connect();
933     /* First we have to split the string into empty lines.
934        An empty line indicates an new Entry */
935     $entries = preg_split("/\n/",$str_attr);
937     $data = "";
938     $cnt = 0; 
939     $current_line = 0;
941     /* FIX ldif */
942     $last = "";
943     $tmp  = "";
944     $i = 0;
945     foreach($entries as $entry){
946       if(preg_match("/^ /",$entry)){
947         $tmp[$i] .= trim($entry);
948       }else{
949         $i ++;
950         $tmp[$i] = trim($entry);
951       }
952     }
954     /* Every single line ... */
955     foreach($tmp as $entry) {
956       $current_line ++;
958       /* Removing Spaces to .. 
959          .. test if a new entry begins */
960       $tmp  = str_replace(" ","",$data );
962       /* .. prevent empty lines in an entry */
963       $tmp2 = str_replace(" ","",$entry);
965       /* If the Block ends (Empty Line) */
966       if((empty($entry))&&(!empty($tmp))) {
967         /* Add collected lines as a complete block */
968         $all[$cnt] = $data;
969         $cnt ++;
970         $data ="";
971       } else {
973         /* Append lines ... */
974         if(!empty($tmp2)) {
975           /* check if we need base64_decode for this line */
976           if(strstr($tmp2, "::") !== false)
977           {
978             $encoded = explode("::",$entry);
979             $attr  = trim($encoded[0]);
980             $value = base64_decode(trim($encoded[1]));
981             /* Add linenumber */
982             $data .= $current_line."#".base64_encode($attr.":".$value)."\n";
983           }
984           else
985           {
986             /* Add Linenumber */ 
987             $data .= $current_line."#".base64_encode($entry)."\n";
988           }
989         }
990       }
991     }
993     /* The Data we collected is not in the array all[];
994        For example the Data is stored like this..
996        all[0] = "1#dn : .... \n 
997        2#ObjectType: person \n ...."
998        
999        Now we check every insertblock and try to insert */
1000     foreach ( $all as $single) {
1001       $lineone = preg_split("/\n/",$single);  
1002       $ndn = explode("#", $lineone[0]);
1003       $line = base64_decode($ndn[1]);
1005       $dnn = explode (":",$line,2);
1006       $current_line = $ndn[0];
1007       $dn    = $dnn[0];
1008       $value = $dnn[1];
1010       /* Every block must begin with a dn */
1011       if($dn != "dn") {
1012         $error= sprintf(_("Invalid DN %s: block to be imported should start with 'dn: ...' in line %s"), bold($line), bold($current_line));
1013         return -2;  
1014       }
1016       /* Should we use Modify instead of Add */
1017       $usemodify= false;
1019       /* Delete before insert */
1020       $usermdir= false;
1021     
1022       /* The dn address already exists, Don't delete destination entry, overwrite it */
1023       if (($this->dn_exists($value))&&((!$JustModify)&&(!$DeleteOldEntries))) {
1025         $usermdir = $usemodify = false;
1027       /* Delete old entry first, then add new */
1028       } elseif(($this->dn_exists($value))&&($DeleteOldEntries)){
1030         /* Delete first, then add */
1031         $usermdir = true;        
1033       } elseif(($this->dn_exists($value))&&($JustModify)) {
1034         
1035         /* Modify instead of Add */
1036         $usemodify = true;
1037       }
1038      
1039       /* If we can't Import, return with a file error */
1040       if(!$this->import_single_entry($srp, $single,$usemodify,$usermdir) ) {
1041         $error= sprintf(_("Error while importing DN %s: please check LDIF from line %s on!"), bold($line),
1042                         $current_line);
1043         return UNKNOWN_TOKEN_IN_LDIF_FILE;      }
1044     }
1046     return (INSERT_OK);
1047   }
1050   /* Imports a single entry 
1051       If $delete is true;  The old entry will be deleted if it exists.
1052       if $modify is true;  All variables that are not touched by the new ldif will be kept.
1053       if $modify is false; The new ldif overwrites the old entry, and all untouched attributes get lost.
1054   */
1055   function import_single_entry($srp, $str_attr,$modify,$delete)
1056   {
1057     global $config;
1059     if(!$config){
1060       trigger_error("Can't import ldif, can't read config object.");
1061     }
1062   
1064     if($this->reconnect) $this->connect();
1066     $ret = false;
1067     $rows= preg_split("/\n/",$str_attr);
1068     $data= false;
1070     foreach($rows as $row) {
1071       
1072       /* Check if we use Linenumbers (when import_complete_ldif is called we use
1073          Linenumbers) Linenumbers are use like this 123#attribute : value */
1074       if(!empty($row)) {
1075         if(strpos($row,"#")!=FALSE) {
1077           /* We are using line numbers 
1078              Because there is a # before a : */
1079           $tmp1= explode("#",$row);
1080           $current_line= $tmp1[0];
1081           $row= base64_decode($tmp1[1]);
1082         }
1084         /* Split the line into  attribute  and value */
1085         $attr   = explode(":", $row,2);
1086         $attr[0]= trim($attr[0]);  /* attribute */
1087         $attr[1]= $attr[1];  /* value */
1089         /* Check :: was used to indicate base64_encoded strings */
1090         if($attr[1][0] == ":"){
1091           $attr[1]=trim(preg_replace("/^:/","",$attr[1]));
1092           $attr[1]=base64_decode($attr[1]);
1093         }
1095         $attr[1] = trim($attr[1]);
1097         /* Check for attributes that are used more than once */
1098         if(!isset($data[$attr[0]])) {
1099           $data[$attr[0]]=$attr[1];
1100         } else {
1101           $tmp = $data[$attr[0]];
1103           if(!is_array($tmp)) {
1104             $new[0]=$tmp;
1105             $new[1]=$attr[1];
1106             $datas[$attr[0]]['count']=1;             
1107             $data[$attr[0]]=$new;
1108           } else {
1109             $cnt = $datas[$attr[0]]['count'];           
1110             $cnt ++;
1111             $data[$attr[0]][$cnt]=$attr[1];
1112             $datas[$attr[0]]['count'] = $cnt;
1113           }
1114         }
1115       }
1116     }
1118     /* If dn is an index of data, we should try to insert the data */
1119     if(isset($data['dn'])) {
1121       /* Fix dn */
1122       $tmp = gosa_ldap_explode_dn($data['dn']);
1123       unset($tmp['count']);
1124       $newdn ="";
1125       foreach($tmp as $tm){
1126         $newdn.= trim($tm).",";
1127       }
1128       $newdn = preg_replace("/,$/","",$newdn);
1129       $data['dn'] = $newdn;
1130    
1131       /* Creating Entry */
1132       $this->cd($data['dn']);
1134       /* Delete existing entry */
1135       if($delete){
1136         $this->rmdir_recursive($srp, $data['dn']);
1137       }
1138      
1139       /* Create missing trees */
1140       $this->cd ($this->basedn);
1141       $this->cd($config->current['BASE']);
1142       $this->create_missing_trees($srp, preg_replace("/^[^,]+,/","",$data['dn']));
1143       $this->cd($data['dn']);
1145       $dn = $data['dn'];
1146       unset($data['dn']);
1147       
1148       if(!$modify){
1150         $this->cat($srp, $dn);
1151         if($this->count($srp)){
1152         
1153           /* The destination entry exists, overwrite it with the new entry */
1154           $attrs = $this->fetch($srp);
1155           foreach($attrs as $name => $value ){
1156             if(!is_numeric($name)){
1157               if(in_array($name,array("dn","count"))) continue;
1158               if(!isset($data[$name])){
1159                 $data[$name] = array();
1160               }
1161             }
1162           }
1163           $ret = $this->modify($data);
1164     
1165         }else{
1166     
1167           /* The destination entry doesn't exists, create it */
1168           $ret = $this->add($data);
1169         }
1171       } else {
1172         
1173         /* Keep all vars that aren't touched by this ldif */
1174         $ret = $this->modify($data);
1175       }
1176     }
1178     if (!$this->success()){
1179       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $dn, "", get_class()));
1180     }
1182     return($ret);
1183   }
1185   
1186   function importcsv($str)
1187   {
1188     $lines = preg_split("/\n/",$str);
1189     foreach($lines as $line)
1190     {
1191       /* continue if theres a comment */
1192       if(substr(trim($line),0,1)=="#"){
1193         continue;
1194       }
1196       $line= str_replace ("\t\t","\t",$line);
1197       $line= str_replace ("\t"  ,"," ,$line);
1198       echo $line;
1200       $cells = explode(",",$line )  ;
1201       $linet= str_replace ("\t\t",",",$line);
1202       $cells = preg_split("/\t/",$line);
1203       $count = count($cells);  
1204     }
1206   }
1207   
1208   function get_objectclasses( $force_reload = FALSE)
1209   {
1210     $objectclasses = array();
1211     global $config;
1213     /* Return the cached results. */
1214     if(class_available('session') && session::global_is_set("LDAP_CACHE::get_objectclasses") && !$force_reload){
1215       $objectclasses = session::global_get("LDAP_CACHE::get_objectclasses");
1216       return($objectclasses);
1217     }
1218         
1219           # Get base to look for schema 
1220           $sr = @ldap_read ($this->cid, "", "objectClass=*", array("subschemaSubentry"));
1221           $attr = @ldap_get_entries($this->cid,$sr);
1222           if (!isset($attr[0]['subschemasubentry'][0])){
1223             return array();
1224           }
1225         
1226           /* Get list of objectclasses and fill array */
1227           $nb= $attr[0]['subschemasubentry'][0];
1228           $objectclasses= array();
1229           $sr= ldap_read ($this->cid, $nb, "objectClass=*", array("objectclasses"));
1230           $attrs= ldap_get_entries($this->cid,$sr);
1231           if (!isset($attrs[0])){
1232             return array();
1233           }
1234           foreach ($attrs[0]['objectclasses'] as $val){
1235       if (preg_match('/^[0-9]+$/', $val)){
1236         continue;
1237       }
1238       $name= "OID";
1239       $pattern= explode(' ', $val);
1240       $ocname= preg_replace("/^.* NAME\s+\(*\s*'([^']+)'\s*\)*.*$/", '\\1', $val);
1241       $objectclasses[$ocname]= array();
1243       foreach($pattern as $chunk){
1244         switch($chunk){
1246           case '(':
1247                     $value= "";
1248                     break;
1250           case ')': if ($name != ""){
1251                       $v = $this->value2container($value);
1252                       if(in_array($name, array('MUST', 'MAY')) && !is_array($v)){
1253                         $v = array($v);
1254                       }
1255                       $objectclasses[$ocname][$name]= $v;
1256                     }
1257                     $name= "";
1258                     $value= "";
1259                     break;
1261           case 'NAME':
1262           case 'DESC':
1263           case 'SUP':
1264           case 'STRUCTURAL':
1265           case 'ABSTRACT':
1266           case 'AUXILIARY':
1267           case 'MUST':
1268           case 'MAY':
1269                     if ($name != ""){
1270                       $v = $this->value2container($value);
1271                       if(in_array($name, array('MUST', 'MAY')) && !is_array($v)){
1272                         $v = array($v);
1273                       }
1274                       $objectclasses[$ocname][$name]= $v;
1275                     }
1276                     $name= $chunk;
1277                     $value= "";
1278                     break;
1280           default:  $value.= $chunk." ";
1281         }
1282       }
1284           }
1285     if(class_available("session")){
1286       session::global_set("LDAP_CACHE::get_objectclasses",$objectclasses);
1287     }
1289           return $objectclasses;
1290   }
1293   function value2container($value)
1294   {
1295     /* Set emtpy values to "true" only */
1296     if (preg_match('/^\s*$/', $value)){
1297       return true;
1298     }
1300     /* Remove ' and " if needed */
1301     $value= preg_replace('/^[\'"]/', '', $value);
1302     $value= preg_replace('/[\'"] *$/', '', $value);
1304     /* Convert to array if $ is inside... */
1305     if (preg_match('/\$/', $value)){
1306       $container= preg_split('/\s*\$\s*/', $value);
1307     } else {
1308       $container= chop($value);
1309     }
1311     return ($container);
1312   }
1315   function log($string)
1316   {
1317     if (session::global_is_set('config')){
1318       $cfg = session::global_get('config');
1319       if (isset($cfg->current['LDAPSTATS']) && preg_match('/true/i', $cfg->current['LDAPSTATS'])){
1320         syslog (LOG_INFO, $string);
1321       }
1322     }
1323   }
1325   /* added by Guido Serra aka Zeph <zeph@purotesto.it> */
1326   function getCn($dn){
1327     $simple= explode(",", $dn);
1329     foreach($simple as $piece) {
1330       $partial= explode("=", $piece);
1332       if($partial[0] == "cn"){
1333         return $partial[1];
1334       }
1335     }
1336   }
1339   function get_naming_contexts($server, $admin= "", $password= "")
1340   {
1341     /* Build LDAP connection */
1342     $ds= ldap_connect ($server);
1343     if (!$ds) {
1344       die ("Can't bind to LDAP. No check possible!");
1345     }
1346     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1347     $r= ldap_bind ($ds, $admin, $password);
1349     /* Get base to look for naming contexts */
1350     $sr  = @ldap_read ($ds, "", "objectClass=*", array("+"));
1351     $attr= @ldap_get_entries($ds,$sr);
1353     return ($attr[0]['namingcontexts']);
1354   }
1357   function get_root_dse($server, $admin= "", $password= "")
1358   {
1359     /* Build LDAP connection */
1360     $ds= ldap_connect ($server);
1361     if (!$ds) {
1362       die ("Can't bind to LDAP. No check possible!");
1363     }
1364     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1365     $r= ldap_bind ($ds, $admin, $password);
1367     /* Get base to look for naming contexts */
1368     $sr  = @ldap_read ($ds, "", "objectClass=*", array("+"));
1369     $attr= @ldap_get_entries($ds,$sr);
1370    
1371     /* Return empty array, if nothing was set */
1372     if (!isset($attr[0])){
1373       return array();
1374     }
1376     /* Rework array... */
1377     $result= array();
1378     for ($i= 0; $i<$attr[0]['count']; $i++){
1379       $result[$attr[0][$i]]= $attr[0][$attr[0][$i]];
1380       unset($result[$attr[0][$i]]['count']);
1381     }
1383     return ($result);
1384   }
1387 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1388 ?>