Code

9b968fd9d91b79598919fbaa6c8e2a2114be5446
[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("ldapMaxQueryTime") != ""){
64       $str = $config->get_cfg_value("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($this->basedn);
212     }
213     return(ereg_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();
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 = get_MicroTimeDiff($start,microtime());
232         if($diff > $this->max_ldap_query_time){
233           msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took about %.2fs!"), $diff), WARNING_DIALOG);
234         }
235       }
237       $this->log("LDAP operation: time=".get_MicroTimeDiff($start,microtime())." operation=search('".LDAP::fix($this->basedn)."', '$filter')");
238       return($this->sr[$srp]);
239     }else{
240       $this->error = "Could not connect to LDAP server";
241       return("");
242     }
243   }
245   function ls($srp, $filter = "(objectclass=*)", $basedn = "",$attrs = array("*"))
246   {
247     if($this->hascon){
248       if ($this->reconnect) $this->connect();
250       $this->clearResult($srp);
251       if ($basedn == "")
252         $basedn = $this->basedn;
253       else
254         $basedn= LDAP::convert($basedn);
255   
256       $start = microtime();
257       $this->sr[$srp] = @ldap_list($this->cid, LDAP::fix($basedn), $filter,$attrs);
258       $this->error = @ldap_error($this->cid);
259       $this->resetResult($srp);
260       $this->hasres[$srp]=true;
262        /* Check if query took longer as specified in max_ldap_query_time */
263       if($this->max_ldap_query_time){
264         $diff = get_MicroTimeDiff($start,microtime());
265         if($diff > $this->max_ldap_query_time){
266           msg_dialog::display(_("Performance warning"), sprintf(_("LDAP performance is poor: last query took about %.2fs!"), $diff), WARNING_DIALOG);
267         }
268       }
270       $this->log("LDAP operation: time=".get_MicroTimeDiff($start,microtime())." operation=ls('".LDAP::fix($basedn)."', '$filter')");
272       return($this->sr[$srp]);
273     }else{
274       $this->error = "Could not connect to LDAP server";
275       return("");
276     }
277   }
279   function cat($srp, $dn,$attrs= array("*"))
280   {
281     if($this->hascon){
282       if ($this->reconnect) $this->connect();
284       $this->clearResult($srp);
285       $filter = "(objectclass=*)";
286       $this->sr[$srp] = @ldap_read($this->cid, LDAP::fix($dn), $filter,$attrs);
287       $this->error = @ldap_error($this->cid);
288       $this->resetResult($srp);
289       $this->hasres[$srp]=true;
290       return($this->sr[$srp]);
291     }else{
292       $this->error = "Could not connect to LDAP server";
293       return("");
294     }
295   }
297   function object_match_filter($dn,$filter)
298   {
299     if($this->hascon){
300       if ($this->reconnect) $this->connect();
301       $res =  @ldap_read($this->cid, LDAP::fix($dn), $filter, array("objectClass"));
302       $rv =   @ldap_count_entries($this->cid, $res);
303       return($rv);
304     }else{
305       $this->error = "Could not connect to LDAP server";
306       return(FALSE);
307     }
308   }
310   function set_size_limit($size)
311   {
312     /* Ignore zero settings */
313     if ($size == 0){
314       @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, 10000000);
315     }
316     if($this->hascon){
317       @ldap_set_option($this->cid, LDAP_OPT_SIZELIMIT, $size);
318     } else {
319       $this->error = "Could not connect to LDAP server";
320     }
321   }
323   function fetch($srp)
324   {
325     $att= array();
326     if($this->hascon){
327       if($this->hasres[$srp]){
328         if ($this->start[$srp] == 0)
329         {
330           if ($this->sr[$srp]){
331             $this->start[$srp] = 1;
332             $this->re[$srp]= @ldap_first_entry($this->cid, $this->sr[$srp]);
333           } else {
334             return array();
335           }
336         } else {
337           $this->re[$srp]= @ldap_next_entry($this->cid, $this->re[$srp]);
338         }
339         if ($this->re[$srp])
340         {
341           $att= @ldap_get_attributes($this->cid, $this->re[$srp]);
342           $att['dn']= trim(LDAP::convert(@ldap_get_dn($this->cid, $this->re[$srp])));
343         }
344         $this->error = @ldap_error($this->cid);
345         if (!isset($att)){
346           $att= array();
347         }
348         return($att);
349       }else{
350         $this->error = "Perform a fetch with no search";
351         return("");
352       }
353     }else{
354       $this->error = "Could not connect to LDAP server";
355       return("");
356     }
357   }
359   function resetResult($srp)
360   {
361     $this->start[$srp] = 0;
362   }
364   function clearResult($srp)
365   {
366     if($this->hasres[$srp]){
367       $this->hasres[$srp] = false;
368       @ldap_free_result($this->sr[$srp]);
369     }
370   }
372   function getDN($srp)
373   {
374     if($this->hascon){
375       if($this->hasres[$srp]){
377         if(!$this->re[$srp])
378           {
379           $this->error = "Perform a Fetch with no valid Result";
380           }
381           else
382           {
383           $rv = @ldap_get_dn($this->cid, $this->re[$srp]);
384         
385           $this->error = @ldap_error($this->cid);
386           return(trim(LDAP::convert($rv)));
387            }
388       }else{
389         $this->error = "Perform a Fetch with no Search";
390         return("");
391       }
392     }else{
393       $this->error = "Could not connect to LDAP server";
394       return("");
395     }
396   }
398   function count($srp)
399   {
400     if($this->hascon){
401       if($this->hasres[$srp]){
402         $rv = @ldap_count_entries($this->cid, $this->sr[$srp]);
403         $this->error = @ldap_error($this->cid);
404         return($rv);
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 rm($attrs = "", $dn = "")
416   {
417     if($this->hascon){
418       if ($this->reconnect) $this->connect();
419       if ($dn == "")
420         $dn = $this->basedn;
422       $r = ldap_mod_del($this->cid, LDAP::fix($dn), $attrs);
423       $this->error = @ldap_error($this->cid);
424       return($r);
425     }else{
426       $this->error = "Could not connect to LDAP server";
427       return("");
428     }
429   }
431   function mod_add($attrs = "", $dn = "")
432   {
433     if($this->hascon){
434       if ($this->reconnect) $this->connect();
435       if ($dn == "")
436         $dn = $this->basedn;
438       $r = @ldap_mod_add($this->cid, LDAP::fix($dn), $attrs);
439       $this->error = @ldap_error($this->cid);
440       return($r);
441     }else{
442       $this->error = "Could not connect to LDAP server";
443       return("");
444     }
445   }
447   function rename($attrs, $dn = "")
448   {
449     if($this->hascon){
450       if ($this->reconnect) $this->connect();
451       if ($dn == "")
452         $dn = $this->basedn;
454       $r = @ldap_mod_replace($this->cid, LDAP::fix($dn), $attrs);
455       $this->error = @ldap_error($this->cid);
456       return($r);
457     }else{
458       $this->error = "Could not connect to LDAP server";
459       return("");
460     }
461   }
463   function rmdir($deletedn)
464   {
465     if($this->hascon){
466       if ($this->reconnect) $this->connect();
467       $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
468       $this->error = @ldap_error($this->cid);
469       return($r ? $r : 0);
470     }else{
471       $this->error = "Could not connect to LDAP server";
472       return("");
473     }
474   }
477   /*! \brief Move the given Ldap entry from $source to $dest
478       @param  String  $source The source dn.
479       @param  String  $dest   The destination dn.
480       @return Boolean TRUE on success else FALSE.
481    */
482   function rename_dn($source,$dest)
483   {
484     /* Check if source and destination are the same entry */
485     if(strtolower($source) == strtolower($dest)){
486       trigger_error("Source and destination can't be the same entry.");
487       $this->error = "Source and destination can't be the same entry.";
488       return(FALSE);
489     }
491     /* Check if destination entry exists */    
492     if($this->dn_exists($dest)){
493       trigger_error("Destination '$dest' already exists.");
494       $this->error = "Destination '$dest' already exists.";
495       return(FALSE);
496     }
498     /* Extract the name and the parent part out ouf source dn.
499         e.g.  cn=herbert,ou=department,dc=... 
500          parent   =>  ou=department,dc=...
501          dest_rdn =>  cn=herbert
502      */
503     $parent   = preg_replace("/^[^,]+,/","", $dest);
504     $dest_rdn = preg_replace("/,.*$/","",$dest);
506     if($this->hascon){
507       if ($this->reconnect) $this->connect();
508       $r= ldap_rename($this->cid,@LDAP::fix($source), @LDAP::fix($dest_rdn),@LDAP::fix($parent),TRUE); 
509       $this->error = ldap_error($this->cid);
511       /* Check if destination dn exists, if not the 
512           server may not support this operation */
513       $r &= is_resource($this->dn_exists($dest));
514       return($r);
515     }else{
516       $this->error = "Could not connect to LDAP server";
517       return(FALSE);
518     }
519   }
522   /**
523   *  Function rmdir_recursive
524   *
525   *  Description: Based in recursive_remove, adding two thing: full subtree remove, and delete own node.
526   *  Parameters:  The dn to delete
527   *  GiveBack:    True on sucessfull , 0 in error, and "" when we don't get a ldap conection
528   *
529   */
530   function rmdir_recursive($srp, $deletedn)
531   {
532     if($this->hascon){
533       if ($this->reconnect) $this->connect();
534       $delarray= array();
535         
536       /* Get sorted list of dn's to delete */
537       $this->ls ($srp, "(objectClass=*)",$deletedn);
538       while ($this->fetch($srp)){
539         $deldn= $this->getDN($srp);
540         $delarray[$deldn]= strlen($deldn);
541       }
542       arsort ($delarray);
543       reset ($delarray);
545       /* Really Delete ALL dn's in subtree */
546       foreach ($delarray as $key => $value){
547         $this->rmdir_recursive($srp, $key);
548       }
549       
550       /* Finally Delete own Node */
551       $r = @ldap_delete($this->cid, LDAP::fix($deletedn));
552       $this->error = @ldap_error($this->cid);
553       return($r ? $r : 0);
554     }else{
555       $this->error = "Could not connect to LDAP server";
556       return("");
557     }
558   }
561   function modify($attrs)
562   {
563     if(count($attrs) == 0){
564       return (0);
565     }
566     if($this->hascon){
567       if ($this->reconnect) $this->connect();
568       $r = @ldap_modify($this->cid, LDAP::fix($this->basedn), $attrs);
569       $this->error = @ldap_error($this->cid);
570       if(!$this->success() && preg_match("/^objectClass: value #([0-9]*) invalid per syntax$/", $this->get_additional_error())){
571         $oc = preg_replace("/^objectClass: value #([0-9]*) invalid per syntax$/","\\1", $this->get_additional_error());
572         if(isset($attrs['objectClass'][$oc])){
573           $this->error.= " <b>objectClass: ".$attrs['objectClass'][$oc]."</b>"; 
574         }
575       }
576       return($r ? $r : 0);
577     }else{
578       $this->error = "Could not connect to LDAP server";
579       return("");
580     }
581   }
583   function add($attrs)
584   {
585     if($this->hascon){
586       if ($this->reconnect) $this->connect();
587       $r = @ldap_add($this->cid, LDAP::fix($this->basedn), $attrs);
588       $this->error = @ldap_error($this->cid);
589       if(!$this->success() && preg_match("/^objectClass: value #([0-9]*) invalid per syntax$/", $this->get_additional_error())){
590         $oc = preg_replace("/^objectClass: value #([0-9]*) invalid per syntax$/","\\1", $this->get_additional_error());
591         if(isset($attrs['objectClass'][$oc])){
592           $this->error.= " <b>objectClass: ".$attrs['objectClass'][$oc]."</b>"; 
593         }
594       }
595       return($r ? $r : 0);
596     }else{
597       $this->error = "Could not connect to LDAP server";
598       return("");
599     }
600   }
602   function create_missing_trees($srp, $target)
603   {
604     global $config;
606     $real_path= substr($target, 0, strlen($target) - strlen($this->basedn) -1 );
608     if ($target == $this->basedn){
609       $l= array("dummy");
610     } else {
611       $l= array_reverse(gosa_ldap_explode_dn($real_path));
612     }
613     unset($l['count']);
614     $cdn= $this->basedn;
615     $tag= "";
617     /* Load schema if available... */
618     $classes= $this->get_objectclasses();
620     foreach ($l as $part){
621       if ($part != "dummy"){
622         $cdn= "$part,$cdn";
623       }
625       /* Ignore referrals */
626       $found= false;
627       foreach($this->referrals as $ref){
628         $base= preg_replace('!^[^:]+://[^/]+/([^?]+).*$!', '\\1', $ref['URI']);
629         if ($base == $cdn){
630           $found= true;
631           break;
632         }
633       }
634       if ($found){
635         continue;
636       }
638       $this->cat ($srp, $cdn);
639       $attrs= $this->fetch($srp);
641       /* Create missing entry? */
642       if (count ($attrs)){
643       
644         /* Catch the tag - if present */
645         if (isset($attrs['gosaUnitTag'][0])){
646           $tag= $attrs['gosaUnitTag'][0];
647         }
649       } else {
650         $type= preg_replace('/^([^=]+)=.*$/', '\\1', $cdn);
651         $param= preg_replace('/^[^=]+=([^,]+).*$/', '\\1', $cdn);
653         $na= array();
655         /* Automatic or traditional? */
656         if(count($classes)){
658           /* Get name of first matching objectClass */
659           $ocname= "";
660           foreach($classes as $class){
661             if (isset($class['MUST']) && $class['MUST'] == "$type"){
663               /* Look for first classes that is structural... */
664               if (isset($class['STRUCTURAL'])){
665                 $ocname= $class['NAME'];
666                 break;
667               }
669               /* Look for classes that are auxiliary... */
670               if (isset($class['AUXILIARY'])){
671                 $ocname= $class['NAME'];
672               }
673             }
674           }
676           /* Bail out, if we've nothing to do... */
677           if ($ocname == ""){
678             msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN '%s': no object class found!"),$type), FATAL_ERROR_DIALOG);
679             exit();
680           }
682           /* Assemble_entry */
683           if ($tag != ""){
684             $na['objectClass']= array($ocname, "gosaAdministrativeUnitTag");
685             $na["gosaUnitTag"]= $tag;
686           } else {
687             $na['objectClass']= array($ocname);
688           }
689           if (isset($classes[$ocname]['AUXILIARY'])){
690             $na['objectClass'][]= $classes[$ocname]['SUP'];
691           }
692           if ($type == "dc"){
693             /* This is bad actually, but - tell me a better way? */
694             $na['objectClass'][]= 'locality';
695           }
696           $na[$type]= $param;
697           if (is_array($classes[$ocname]['MUST'])){
698             foreach($classes[$ocname]['MUST'] as $attr){
699               $na[$attr]= "filled";
700             }
701           }
703         } else {
705           /* Use alternative add... */
706           switch ($type){
707             case 'ou':
708               if ($tag != ""){
709                 $na["objectClass"]= array("organizationalUnit", "gosaAdministrativeUnitTag");
710                 $na["gosaUnitTag"]= $tag;
711               } else {
712                 $na["objectClass"]= "organizationalUnit";
713               }
714               $na["ou"]= $param;
715               break;
716             case 'dc':
717               if ($tag != ""){
718                 $na["objectClass"]= array("dcObject", "top", "locality", "gosaAdministrativeUnitTag");
719                 $na["gosaUnitTag"]= $tag;
720               } else {
721                 $na["objectClass"]= array("dcObject", "top", "locality");
722               }
723               $na["dc"]= $param;
724               break;
725             default:
726               msg_dialog::display(_("Internal error"), sprintf(_("Cannot automatically create subtrees with RDN '%s': not supported"),$type), FATAL_ERROR_DIALOG);
727               exit();
728           }
730         }
731         $this->cd($cdn);
732         $this->add($na);
733     
734         if (!$this->success()){
736           print_a(array($cdn,$na));
738           msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $cdn, LDAP_ADD, get_class()));
739           return FALSE;
740         }
741       }
742     }
744     return TRUE;
745   }
748   function recursive_remove($srp)
749   {
750     $delarray= array();
752     /* Get sorted list of dn's to delete */
753     $this->search ($srp, "(objectClass=*)");
754     while ($this->fetch($srp)){
755       $deldn= $this->getDN($srp);
756       $delarray[$deldn]= strlen($deldn);
757     }
758     arsort ($delarray);
759     reset ($delarray);
761     /* Delete all dn's in subtree */
762     foreach ($delarray as $key => $value){
763       $this->rmdir($key);
764     }
765   }
768   function get_attribute($dn, $name,$r_array=0)
769   {
770     $data= "";
771     if ($this->reconnect) $this->connect();
772     $sr= @ldap_read($this->cid, LDAP::fix($dn), "objectClass=*", array("$name"));
774     /* fill data from LDAP */
775     if ($sr) {
776       $ei= @ldap_first_entry($this->cid, $sr);
777       if ($ei) {
778         if ($info= @ldap_get_values_len($this->cid, $ei, "$name")){
779           $data= $info[0];
780         }
781       }
782     }
783     if($r_array==0) {
784       return ($data);
785     } else {
786       return ($info);
787     }
788   }
789  
792   function get_additional_error()
793   {
794     $error= "";
795     @ldap_get_option ($this->cid, LDAP_OPT_ERROR_STRING, $error);
796     return ($error);
797   }
800   function success()
801   {
802     return (preg_match('/Success/i', $this->error));
803   }
806   function get_error()
807   {
808     if ($this->error == 'Success'){
809       return $this->error;
810     } else {
811       $adderror= $this->get_additional_error();
812       if ($adderror != ""){
813         $error= $this->error." (".$this->get_additional_error().", ".sprintf(_("while operating on '%s' using LDAP server '%s'"), $this->basedn, $this->hostname).")";
814       } else {
815         $error= $this->error." (".sprintf(_("while operating on LDAP server %s"), $this->hostname).")";
816       }
817       return $error;
818     }
819   }
821   function get_credentials($url, $referrals= NULL)
822   {
823     $ret= array();
824     $url= preg_replace('!\?\?.*$!', '', $url);
825     $server= preg_replace('!^([^:]+://[^/]+)/.*$!', '\\1', $url);
827     if ($referrals === NULL){
828       $referrals= $this->referrals;
829     }
831     if (isset($referrals[$server])){
832       return ($referrals[$server]);
833     } else {
834       $ret['ADMINDN']= LDAP::fix($this->binddn);
835       $ret['ADMINPASSWORD']= $this->bindpw;
836     }
838     return ($ret);
839   }
842   function gen_ldif ($srp, $dn, $filter= "(objectClass=*)", $attributes= array('*'), $recursive= TRUE)
843   {
844     $display= "";
846     if ($recursive){
847       $this->cd($dn);
848       $this->ls($srp, $filter,$dn, array('dn','objectClass'));
849       $deps = array();
851       $display .= $this->gen_one_entry($dn)."\n";
853       while ($attrs= $this->fetch($srp)){
854         $deps[] = $attrs['dn'];
855       }
856       foreach($deps as $dn){
857         $display .= $this->gen_ldif($srp, $dn, $filter,$attributes,$recursive);
858       }
859     } else {
860       $display.= $this->gen_one_entry($dn);
861     }
862     return ($display);
863   }
866   function gen_xls ($srp, $dn, $filter= "(objectClass=*)", $attributes= array('*'), $recursive= TRUE,$r_array=0)
867   {
868     $display= array();
870       $this->cd($dn);
871       $this->search($srp, "$filter");
873       $i=0;
874       while ($attrs= $this->fetch($srp)){
875         $j=0;
877         foreach ($attributes as $at){
878           $display[$i][$j]= $this->get_attribute($attrs['dn'], $at,$r_array);
879           $j++;
880         }
882         $i++;
883       }
885     return ($display);
886   }
889   function gen_one_entry($dn, $filter= "(objectClass=*)" , $name= array("*"))
890   {
891     $ret = "";
892     $data = "";
893     if($this->reconnect){
894       $this->connect();
895     }
897     /* Searching Ldap Tree */
898     $sr= @ldap_read($this->cid, LDAP::fix($dn), $filter, $name);
900     /* Get the first entry */   
901     $entry= @ldap_first_entry($this->cid, $sr);
903     /* Get all attributes related to that Objekt */
904     $atts = array();
905     
906     /* Assemble dn */
907     $atts[0]['name']  = "dn";
908     $atts[0]['value'] = array('count' => 1, 0 => $dn);
910     /* Reset index */
911     $i = 1 ; 
912   $identifier = array();
913     $attribute= @ldap_first_attribute($this->cid,$entry,$identifier);
914     while ($attribute) {
915       $i++;
916       $atts[$i]['name']  = $attribute;
917       $atts[$i]['value'] = @ldap_get_values_len($this->cid, $entry, "$attribute");
919       /* Next one */
920       $attribute= @ldap_next_attribute($this->cid,$entry,$identifier);
921     }
923     foreach($atts as $at)
924     {
925       for ($i= 0; $i<$at['value']['count']; $i++){
927         /* Check if we must encode the data */
928         if(!preg_match('/^[a-z0-9+@#.=, \/ -]+$/i', $at['value'][$i])) {
929           $ret .= $at['name'].":: ".base64_encode($at['value'][$i])."\n";
930         } else {
931           $ret .= $at['name'].": ".$at['value'][$i]."\n";
932         }
933       }
934     }
936     return($ret);
937   }
940   function dn_exists($dn)
941   {
942     return @ldap_list($this->cid, LDAP::fix($dn), "(objectClass=*)", array("objectClass"));
943   }
944   
947   /*  This funktion imports ldifs 
948         
949       If DeleteOldEntries is true, the destination entry will be deleted first. 
950       If JustModify is true the destination entry will only be touched by the attributes specified in the ldif.
951       if JustMofify id false the destination dn will be overwritten by the new ldif. 
952     */
954   function import_complete_ldif($srp, $str_attr,$error,$JustModify,$DeleteOldEntries)
955   {
956     if($this->reconnect) $this->connect();
958     /* First we have to splitt the string ito detect empty lines
959        An empty line indicates an new Entry */
960     $entries = split("\n",$str_attr);
962     $data = "";
963     $cnt = 0; 
964     $current_line = 0;
966     /* FIX ldif */
967     $last = "";
968     $tmp  = "";
969     $i = 0;
970     foreach($entries as $entry){
971       if(preg_match("/^ /",$entry)){
972         $tmp[$i] .= trim($entry);
973       }else{
974         $i ++;
975         $tmp[$i] = trim($entry);
976       }
977     }
979     /* Every single line ... */
980     foreach($tmp as $entry) {
981       $current_line ++;
983       /* Removing Spaces to .. 
984          .. test if a new entry begins */
985       $tmp  = str_replace(" ","",$data );
987       /* .. prevent empty lines in an entry */
988       $tmp2 = str_replace(" ","",$entry);
990       /* If the Block ends (Empty Line) */
991       if((empty($entry))&&(!empty($tmp))) {
992         /* Add collected lines as a complete block */
993         $all[$cnt] = $data;
994         $cnt ++;
995         $data ="";
996       } else {
998         /* Append lines ... */
999         if(!empty($tmp2)) {
1000           /* check if we need base64_decode for this line */
1001           if(ereg("::",$tmp2))
1002           {
1003             $encoded = split("::",$entry);
1004             $attr  = trim($encoded[0]);
1005             $value = base64_decode(trim($encoded[1]));
1006             /* Add linenumber */
1007             $data .= $current_line."#".base64_encode($attr.":".$value)."\n";
1008           }
1009           else
1010           {
1011             /* Add Linenumber */ 
1012             $data .= $current_line."#".base64_encode($entry)."\n";
1013           }
1014         }
1015       }
1016     }
1018     /* The Data we collected is not in the array all[];
1019        For example the Data is stored like this..
1021        all[0] = "1#dn : .... \n 
1022        2#ObjectType: person \n ...."
1023        
1024        Now we check every insertblock and try to insert */
1025     foreach ( $all as $single) {
1026       $lineone = split("\n",$single);  
1027       $ndn = split("#", $lineone[0]);
1028       $line = base64_decode($ndn[1]);
1030       $dnn = split (":",$line,2);
1031       $current_line = $ndn[0];
1032       $dn    = $dnn[0];
1033       $value = $dnn[1];
1035       /* Every block must begin with a dn */
1036       if($dn != "dn") {
1037         $error= sprintf(_("This is not a valid DN: '%s'. A block for import should begin with 'dn: ...' in line %s"), $line, $current_line);
1038         return -2;  
1039       }
1041       /* Should we use Modify instead of Add */
1042       $usemodify= false;
1044       /* Delete before insert */
1045       $usermdir= false;
1046     
1047       /* The dn address already exists, Don't delete destination entry, overwrite it */
1048       if (($this->dn_exists($value))&&((!$JustModify)&&(!$DeleteOldEntries))) {
1050         $usermdir = $usemodify = false;
1052       /* Delete old entry first, then add new */
1053       } elseif(($this->dn_exists($value))&&($DeleteOldEntries)){
1055         /* Delete first, then add */
1056         $usermdir = true;        
1058       } elseif(($this->dn_exists($value))&&($JustModify)) {
1059         
1060         /* Modify instead of Add */
1061         $usemodify = true;
1062       }
1063      
1064       /* If we can't Import, return with a file error */
1065       if(!$this->import_single_entry($srp, $single,$usemodify,$usermdir) ) {
1066         $error= sprintf(_("Error while importing dn: '%s', please check your LDIF from line %s on!"), $line,
1067                         $current_line);
1068         return UNKNOWN_TOKEN_IN_LDIF_FILE;      }
1069     }
1071     return (INSERT_OK);
1072   }
1075   /* Imports a single entry 
1076       If $delete is true;  The old entry will be deleted if it exists.
1077       if $modify is true;  All variables that are not touched by the new ldif will be kept.
1078       if $modify is false; The new ldif overwrites the old entry, and all untouched attributes get lost.
1079   */
1080   function import_single_entry($srp, $str_attr,$modify,$delete)
1081   {
1082     global $config;
1084     if(!$config){
1085       trigger_error("Can't import ldif, can't read config object.");
1086     }
1087   
1089     if($this->reconnect) $this->connect();
1091     $ret = false;
1092     $rows= split("\n",$str_attr);
1093     $data= false;
1095     foreach($rows as $row) {
1096       
1097       /* Check if we use Linenumbers (when import_complete_ldif is called we use
1098          Linenumbers) Linenumbers are use like this 123#attribute : value */
1099       if(!empty($row)) {
1100         if(strpos($row,"#")!=FALSE) {
1102           /* We are using line numbers 
1103              Because there is a # before a : */
1104           $tmp1= split("#",$row);
1105           $current_line= $tmp1[0];
1106           $row= base64_decode($tmp1[1]);
1107         }
1109         /* Split the line into  attribute  and value */
1110         $attr   = split(":", $row,2);
1111         $attr[0]= trim($attr[0]);  /* attribute */
1112         $attr[1]= $attr[1];  /* value */
1114         /* Check :: was used to indicate base64_encoded strings */
1115         if($attr[1][0] == ":"){
1116           $attr[1]=trim(preg_replace("/^:/","",$attr[1]));
1117           $attr[1]=base64_decode($attr[1]);
1118         }
1120         $attr[1] = trim($attr[1]);
1122         /* Check for attributes that are used more than once */
1123         if(!isset($data[$attr[0]])) {
1124           $data[$attr[0]]=$attr[1];
1125         } else {
1126           $tmp = $data[$attr[0]];
1128           if(!is_array($tmp)) {
1129             $new[0]=$tmp;
1130             $new[1]=$attr[1];
1131             $datas[$attr[0]]['count']=1;             
1132             $data[$attr[0]]=$new;
1133           } else {
1134             $cnt = $datas[$attr[0]]['count'];           
1135             $cnt ++;
1136             $data[$attr[0]][$cnt]=$attr[1];
1137             $datas[$attr[0]]['count'] = $cnt;
1138           }
1139         }
1140       }
1141     }
1143     /* If dn is an index of data, we should try to insert the data */
1144     if(isset($data['dn'])) {
1146       /* Fix dn */
1147       $tmp = gosa_ldap_explode_dn($data['dn']);
1148       unset($tmp['count']);
1149       $newdn ="";
1150       foreach($tmp as $tm){
1151         $newdn.= trim($tm).",";
1152       }
1153       $newdn = preg_replace("/,$/","",$newdn);
1154       $data['dn'] = $newdn;
1155    
1156       /* Creating Entry */
1157       $this->cd($data['dn']);
1159       /* Delete existing entry */
1160       if($delete){
1161         $this->rmdir_recursive($srp, $data['dn']);
1162       }
1163      
1164       /* Create missing trees */
1165       $this->cd ($this->basedn);
1166       $this->cd($config->current['BASE']);
1167       $this->create_missing_trees($srp, preg_replace("/^[^,]+,/","",$data['dn']));
1168       $this->cd($data['dn']);
1170       $dn = $data['dn'];
1171       unset($data['dn']);
1172       
1173       if(!$modify){
1175         $this->cat($srp, $dn);
1176         if($this->count($srp)){
1177         
1178           /* The destination entry exists, overwrite it with the new entry */
1179           $attrs = $this->fetch($srp);
1180           foreach($attrs as $name => $value ){
1181             if(!is_numeric($name)){
1182               if(in_array($name,array("dn","count"))) continue;
1183               if(!isset($data[$name])){
1184                 $data[$name] = array();
1185               }
1186             }
1187           }
1188           $ret = $this->modify($data);
1189     
1190         }else{
1191     
1192           /* The destination entry doesn't exists, create it */
1193           $ret = $this->add($data);
1194         }
1196       } else {
1197         
1198         /* Keep all vars that aren't touched by this ldif */
1199         $ret = $this->modify($data);
1200       }
1201     }
1203     if (!$this->success()){
1204       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($this->get_error(), $dn, "", get_class()));
1205     }
1207     return($ret);
1208   }
1210   
1211   function importcsv($str)
1212   {
1213     $lines = split("\n",$str);
1214     foreach($lines as $line)
1215     {
1216       /* continue if theres a comment */
1217       if(substr(trim($line),0,1)=="#"){
1218         continue;
1219       }
1221       $line= str_replace ("\t\t","\t",$line);
1222       $line= str_replace ("\t"  ,"," ,$line);
1223       echo $line;
1225       $cells = split(",",$line )  ;
1226       $linet= str_replace ("\t\t",",",$line);
1227       $cells = split("\t",$line);
1228       $count = count($cells);  
1229     }
1231   }
1232   
1233   function get_objectclasses( $force_reload = FALSE)
1234   {
1235     $objectclasses = array();
1236     global $config;
1238     /* Only read schema if it is allowed */
1239     if(isset($config) && preg_match("/config/i",get_class($config))){
1240       if ($config->get_cfg_value("schemaCheck") != "true"){
1241         return($objectclasses);
1242       } 
1243     }
1245     /* Return the cached results. */
1246     if(class_available('session') && session::global_is_set("LDAP_CACHE::get_objectclasses") && !$force_reload){
1247       $objectclasses = session::global_get("LDAP_CACHE::get_objectclasses");
1248       return($objectclasses);
1249     }
1250         
1251           # Get base to look for schema 
1252           $sr = @ldap_read ($this->cid, "", "objectClass=*", array("subschemaSubentry"));
1253           $attr = @ldap_get_entries($this->cid,$sr);
1254           if (!isset($attr[0]['subschemasubentry'][0])){
1255             return array();
1256           }
1257         
1258           /* Get list of objectclasses and fill array */
1259           $nb= $attr[0]['subschemasubentry'][0];
1260           $objectclasses= array();
1261           $sr= ldap_read ($this->cid, $nb, "objectClass=*", array("objectclasses"));
1262           $attrs= ldap_get_entries($this->cid,$sr);
1263           if (!isset($attrs[0])){
1264             return array();
1265           }
1266           foreach ($attrs[0]['objectclasses'] as $val){
1267       if (preg_match('/^[0-9]+$/', $val)){
1268         continue;
1269       }
1270       $name= "OID";
1271       $pattern= split(' ', $val);
1272       $ocname= preg_replace("/^.* NAME\s+\(*\s*'([^']+)'\s*\)*.*$/", '\\1', $val);
1273       $objectclasses[$ocname]= array();
1275       foreach($pattern as $chunk){
1276         switch($chunk){
1278           case '(':
1279                     $value= "";
1280                     break;
1282           case ')': if ($name != ""){
1283                       $objectclasses[$ocname][$name]= $this->value2container($value);
1284                     }
1285                     $name= "";
1286                     $value= "";
1287                     break;
1289           case 'NAME':
1290           case 'DESC':
1291           case 'SUP':
1292           case 'STRUCTURAL':
1293           case 'ABSTRACT':
1294           case 'AUXILIARY':
1295           case 'MUST':
1296           case 'MAY':
1297                     if ($name != ""){
1298                       $objectclasses[$ocname][$name]= $this->value2container($value);
1299                     }
1300                     $name= $chunk;
1301                     $value= "";
1302                     break;
1304           default:  $value.= $chunk." ";
1305         }
1306       }
1308           }
1309     if(class_available("session")){
1310       session::global_set("LDAP_CACHE::get_objectclasses",$objectclasses);
1311     }
1313           return $objectclasses;
1314   }
1317   function value2container($value)
1318   {
1319     /* Set emtpy values to "true" only */
1320     if (preg_match('/^\s*$/', $value)){
1321       return true;
1322     }
1324     /* Remove ' and " if needed */
1325     $value= preg_replace('/^[\'"]/', '', $value);
1326     $value= preg_replace('/[\'"] *$/', '', $value);
1328     /* Convert to array if $ is inside... */
1329     if (preg_match('/\$/', $value)){
1330       $container= preg_split('/\s*\$\s*/', $value);
1331     } else {
1332       $container= chop($value);
1333     }
1335     return ($container);
1336   }
1339   function log($string)
1340   {
1341     if (session::global_is_set('config')){
1342       $cfg = session::global_get('config');
1343       if (isset($cfg->current['LDAPSTATS']) && preg_match('/true/i', $cfg->current['LDAPSTATS'])){
1344         syslog (LOG_INFO, $string);
1345       }
1346     }
1347   }
1349   /* added by Guido Serra aka Zeph <zeph@purotesto.it> */
1350   function getCn($dn){
1351     $simple= split(",", $dn);
1353     foreach($simple as $piece) {
1354       $partial= split("=", $piece);
1356       if($partial[0] == "cn"){
1357         return $partial[1];
1358       }
1359     }
1360   }
1363   function get_naming_contexts($server, $admin= "", $password= "")
1364   {
1365     /* Build LDAP connection */
1366     $ds= ldap_connect ($server);
1367     if (!$ds) {
1368       die ("Can't bind to LDAP. No check possible!");
1369     }
1370     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1371     $r= ldap_bind ($ds, $admin, $password);
1373     /* Get base to look for naming contexts */
1374     $sr  = @ldap_read ($ds, "", "objectClass=*", array("+"));
1375     $attr= @ldap_get_entries($ds,$sr);
1377     return ($attr[0]['namingcontexts']);
1378   }
1381   function get_root_dse($server, $admin= "", $password= "")
1382   {
1383     /* Build LDAP connection */
1384     $ds= ldap_connect ($server);
1385     if (!$ds) {
1386       die ("Can't bind to LDAP. No check possible!");
1387     }
1388     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
1389     $r= ldap_bind ($ds, $admin, $password);
1391     /* Get base to look for naming contexts */
1392     $sr  = @ldap_read ($ds, "", "objectClass=*", array("+"));
1393     $attr= @ldap_get_entries($ds,$sr);
1394    
1395     /* Return empty array, if nothing was set */
1396     if (!isset($attr[0])){
1397       return array();
1398     }
1400     /* Rework array... */
1401     $result= array();
1402     for ($i= 0; $i<$attr[0]['count']; $i++){
1403       $result[$attr[0][$i]]= $attr[0][$attr[0][$i]];
1404       unset($result[$attr[0][$i]]['count']);
1405     }
1407     return ($result);
1408   }
1411 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1412 ?>