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 }
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;
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);
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]);
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();
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 }
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)){
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);
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 }
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 }
922 /* This funktion imports ldifs
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 ...."
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;
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)) {
1035 /* Modify instead of Add */
1036 $usemodify = true;
1037 }
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 }
1064 if($this->reconnect) $this->connect();
1066 $ret = false;
1067 $rows= preg_split("/\n/",$str_attr);
1068 $data= false;
1070 foreach($rows as $row) {
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;
1131 /* Creating Entry */
1132 $this->cd($data['dn']);
1134 /* Delete existing entry */
1135 if($delete){
1136 $this->rmdir_recursive($srp, $data['dn']);
1137 }
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']);
1148 if(!$modify){
1150 $this->cat($srp, $dn);
1151 if($this->count($srp)){
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);
1165 }else{
1167 /* The destination entry doesn't exists, create it */
1168 $ret = $this->add($data);
1169 }
1171 } else {
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 }
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 }
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 }
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 }
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);
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 }
1386 }
1387 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1388 ?>