class_plugin.inc 43.9 KB
Newer Older
1
2
3
4
5
<?php

/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
6
  Copyright (C) 2011-2013  FusionDirectory
7
8
9
10
11
12
13
14
15
16
17
18
19

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
20
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21
22
*/

23
24
25
26
27
/*!
 * \file class_plugin.inc
 * Source code for the class plugin
 */

28
/*!
29
 * \brief This is the base class for all plugins.
30
 *
Benoit Mortier's avatar
Benoit Mortier committed
31
32
33
 * \author  Cajus Pollmeier <pollmeier@gonicus.de>
 * \version 2.00
 * \date    24.07.2003
34
 *
Benoit Mortier's avatar
Benoit Mortier committed
35
 * This is the base class for all plugins. It can be used standalone or
36
 * can be included by the tabs class. All management should be done
Benoit Mortier's avatar
Benoit Mortier committed
37
 * within this class. Extend your plugins from this class.
38
39
40
41
 */
class plugin
{
  /*!
42
   * \brief Reference to parent object
43
   *
44
45
46
47
48
49
   * This variable is used when the plugin is included in tabs
   * and keeps reference to the tab class. Communication to other
   * tabs is possible by 'name'. So the 'fax' plugin can ask the
   * 'userinfo' plugin for the fax number.
   *
   * \sa tab
50
   */
51
  var $parent = NULL;
52
53
54
55
56
57

  /*!
    \brief Configuration container

    Access to global configuration
   */
58
  var $config = NULL;
59
60
61
62
63
64
65
66
67
68
69
70

  /*!
    \brief Mark plugin as account

    Defines whether this plugin is defined as an account or not.
    This has consequences for the plugin to be saved from tab
    mode. If it is set to 'FALSE' the tab will call the delete
    function, else the save function. Should be set to 'TRUE' if
    the construtor detects a valid LDAP object.

    \sa plugin::plugin()
   */
71
72
  var $is_account             = FALSE;
  var $initially_was_account  = FALSE;
73
74
75
76
77
78
79
80
81
82

  /*!
    \brief Mark plugin as template

    Defines whether we are creating a template or a normal object.
    Has conseqences on the way execute() shows the formular and how
    save() puts the data to LDAP.

    \sa plugin::save() plugin::execute()
   */
83
84
85
  var $is_template    = FALSE;
  var $ignore_account = FALSE;
  var $is_modified    = FALSE;
86
87
88
89
90
91

  /*!
    \brief Represent temporary LDAP data

    This is only used internally.
   */
92
  var $attrs = array();
93
94

  /* Keep set of conflicting plugins */
95
  var $conflicts = array();
96
97
98
99
100
101

  /*!
    \brief Used standard values

    dn
   */
102
103
104
105
106
107
  var $dn         = "";
  var $uid        = "";
  var $sn         = "";
  var $givenName  = "";
  var $acl        = "*none*";
  var $dialog     = FALSE;
108
109

  /* attribute list for save action */
110
111
112
113
  var $attributes       = array();
  var $objectclasses    = array();
  var $is_new           = TRUE;
  var $saved_attributes = array();
114

115
116
117
  var $acl_base     = "";
  var $acl_category = "";
  var $read_only    = FALSE; // Used when the entry is opened as "readonly" due to locks.
118
119

  /* This can be set to render the tabulators in another stylesheet */
120
  var $pl_notify = FALSE;
121
122
123
124
125
126
127

  /* Object entry CSN */
  var $entryCSN         = "";
  var $CSN_check_active = FALSE;

  var $selected_edit_values = array();

128
  /*!
129
130
131
132
133
   * \brief plugin constructor
   *
   * If 'dn' is set, the node loads the given 'dn' from LDAP
   *
   * \param $config configuration
134
   *
135
   * \param $dn Distinguished name to initialize plugin from
136
   *
137
   * \param $object NULL
138
   *
139
   * \sa plugin()
140
   */
141
  function plugin (&$config, $dn = NULL, $object = NULL)
142
143
  {
    /* Configuration is fine, allways */
144
145
    $this->config = &$config;
    $this->dn     = $dn;
146
147

    // Ensure that we've a valid acl_category set.
148
    if (empty($this->acl_category)) {
149
150
151
      $tmp = $this->plInfo();
      if (isset($tmp['plCategory'])) {
        $c = key($tmp['plCategory']);
152
        if (is_numeric($c)) {
153
154
155
156
157
158
159
          $c = $tmp['plCategory'][0];
        }
        $this->acl_category = $c."/";
      }
    }

    /* Handle new accounts, don't read information from LDAP */
160
161
162
163
164
165
166
167
    if ($this->dn != "new") {
      /* Check if this entry was opened in read only mode */
      if (isset($_POST['open_readonly'])) {
        if (session::global_is_set("LOCK_CACHE")) {
          $cache = &session::get("LOCK_CACHE");
          if (isset($cache['READ_ONLY'][$this->dn])) {
            $this->read_only = TRUE;
          }
168
169
170
        }
      }

171
172
173
174
175
176
177
      /* Save current dn as acl_base */
      $this->acl_base = $this->dn;

      if ($this->dn === NULL) {
        return;
      }
    }
178
179

    /* Get LDAP descriptor */
180
    if (($this->dn != "new") || ($object !== NULL)) {
181
182

      /* Load data to 'attrs' and save 'dn' */
183
184
      if ($object !== NULL) {
        $this->attrs = $object->attrs;
185
      } else {
186
187
188
        $ldap = $this->config->get_ldap_link();
        $ldap->cat($this->dn);
        $this->attrs = $ldap->fetch();
189
190
191
      }

      /* Copy needed attributes */
192
193
194
195
      foreach ($this->attributes as $val) {
        $found = array_key_ics($val, $this->attrs);
        if ($found != "") {
          $this->$val = $found[0];
196
197
198
199
200
        }
      }

      /* Set the template flag according to the existence of objectClass
         gosaUserTemplate */
201
202
203
      if (isset($this->attrs['objectClass'])) {
        if (in_array_ics ("gosaUserTemplate", $this->attrs['objectClass'])) {
          $this->is_template = TRUE;
204
205
206
207
208
209
          @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
              "found", "Template check");
        }
      }

      /* Is Account? */
210
      if ($this->is_this_account($this->attrs)) {
211
212
        $this->is_account = TRUE;
        @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "found", "Object check");
213
214
      }

215
216
      $this->prepareSavedAttributes();
    }
217

218
219
220
    /* Save initial account state */
    $this->initially_was_account = $this->is_account;
  }
221

222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
  function is_this_account($attrs)
  {
    $found = TRUE;
    foreach ($this->objectclasses as $obj) {
      if (preg_match('/top/i', $obj)) {
        continue;
      }
      if (!isset($attrs['objectClass']) || !in_array_ics ($obj, $attrs['objectClass'])) {
        $found = FALSE;
        break;
      }
    }
    return $found;
  }

237
238
239
240
  function prepareSavedAttributes()
  {
    /* Prepare saved attributes */
    $this->saved_attributes = $this->attrs;
241
    foreach (array_keys($this->saved_attributes) as $index) {
242
243
244
      if (is_numeric($index)) {
        unset($this->saved_attributes[$index]);
        continue;
245
246
      }

247
248
249
      if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)) {
        unset($this->saved_attributes[$index]);
        continue;
250
      }
251
252
253
254
255
256
257
258
259
260
261
262

      if (isset($this->saved_attributes[$index][0])) {
        if (!isset($this->saved_attributes[$index]["count"])) {
          $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
        }
        if ($this->saved_attributes[$index]["count"] == 1) {
          $tmp = $this->saved_attributes[$index][0];
          unset($this->saved_attributes[$index]);
          $this->saved_attributes[$index] = $tmp;
          continue;
        }
      }
263
      unset($this->saved_attributes[$index]["count"]);
264
265
266
    }
  }

267
268
269
270
271
272
273
274
275
276
277
278
  /*!
   * \brief This function is called on the copied object to set its dn to where it will be saved
   */
  function resetCopyInfos()
  {
    $this->dn = "new";
    $this->saved_attributes = array();

    $this->orig_dn = $this->dn;
    $this->initially_was_account = FALSE;
  }

279

280
  /*!
281
   * \brief Generates the html output for this node
282
283
284
285
   */
  function execute()
  {
    /* This one is empty currently. Fabian - please fill in the docu code */
286
    session::global_set('current_class_for_help', get_class($this));
287
288

    /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
289
290
291
292
    session::set('LOCK_VARS_TO_USE', array());
    session::set('LOCK_VARS_USED_GET', array());
    session::set('LOCK_VARS_USED_POST', array());
    session::set('LOCK_VARS_USED_REQUEST', array());
293
294
  }

295
  /*!
296
   * \brief Removes object from parent
297
298
299
300
   */
  function remove_from_parent()
  {
    /* include global link_info */
301
    $ldap = $this->config->get_ldap_link();
302
303
304

    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);
305
306
307
308
    $tmp  = $ldap->fetch ();
    $oc   = array();
    if (isset($tmp['objectClass'])) {
      $oc = $tmp['objectClass'];
309
310
311
312
313
      unset($oc['count']);
    }

    /* Remove objectClasses from entry */
    $ldap->cd($this->dn);
314
315
    $this->attrs                = array();
    $this->attrs['objectClass'] = array_remove_entries_ics($this->objectclasses, $oc);
316
317

    /* Unset attributes from entry */
318
319
    foreach ($this->attributes as $val) {
      $this->attrs["$val"] = array();
320
321
322
323
324
    }

    /* Do not write in plugin base class, this must be done by
       children, since there are normally additional attribs,
       lists, etc. */
325
326
    if ($this->initially_was_account) {
      $this->handle_pre_events('remove');
327
328
329
330
    }
  }


331
  /*!
332
   * \brief Save HTML posted data to object
333
334
335
336
   */
  function save_object()
  {
    /* Update entry CSN if it is empty. */
337
    if (empty($this->entryCSN) && $this->CSN_check_active) {
338
339
340
341
      $this->entryCSN = getEntryCSN($this->dn);
    }

    /* Save values to object */
342
343
    foreach ($this->attributes as $val) {
      if ($this->acl_is_writeable($val) && isset ($_POST["$val"])) {
344
        /* Check for modifications */
345
        $data = $_POST["$val"];
346
347

        if ($this->$val != $data) {
348
          $this->is_modified = TRUE;
349
        }
350

351
352
        $this->$val = $data;

353
354
355
356
357
        /* Okay, how can I explain this fix ...
         * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds.
         * So IE posts these 'unselectable' option, with value = chr(194)
         * chr(194) seems to be the &nbsp; in between the ...option>&nbsp;</option.. because there is no value=".." specified in these option fields
         * This &nbsp; was added for W3c compliance, but now causes these ... ldap errors ...
358
359
         * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
         */
360
        if (isset($data[0]) && ($data[0] == chr(194))) {
361
          $data = "";
362
        }
363
        $this->$val = $data;
364
365
366
367
368
      }
    }
  }


369
  /*!
370
   * \brief Save data to LDAP, depending on is_account we save or delete
Benoit Mortier's avatar
Benoit Mortier committed
371
   */
372
373
374
  function save()
  {
    /* include global link_info */
375
    $ldap = $this->config->get_ldap_link();
376
377
378
379
380

    /* Save all plugins */
    $this->entryCSN = "";

    /* Start with empty array */
381
    $this->attrs = array();
382
383
384

    /* Get current objectClasses in order to add the required ones */
    $ldap->cat($this->dn);
385

386
    $tmp = $ldap->fetch ();
387

388
389
390
    $oc = array();
    if (isset($tmp['objectClass'])) {
      $oc = $tmp["objectClass"];
391
      unset($oc['count']);
392
      $this->is_new = FALSE;
393
    } else {
394
      $this->is_new = TRUE;
395
396
397
    }

    /* Load (minimum) attributes, add missing ones */
398
    $this->attrs['objectClass'] = array_merge_unique($oc, $this->objectclasses);
399
400

    /* Copy standard attributes */
401
402
403
    foreach ($this->attributes as $val) {
      if ($this->$val != "") {
        $this->attrs["$val"] = $this->$val;
404
      } elseif (!$this->is_new) {
405
        $this->attrs["$val"] = array();
406
407
408
      }
    }

409
410
411
412
    if ($this->is_new) {
      $this->handle_pre_events('add');
    } else {
      $this->handle_pre_events('modify');
413
414
415
    }
  }

416
417
418
419
  /*!
   * \brief Remove attributes, empty arrays, arrays
   * single attributes that do not differ
   */
420
421
  function cleanup()
  {
422
    foreach ($this->attrs as $index => $value) {
423

424
425
      /* Convert arrays with one element to non arrays, if the saved
         attributes are no array, too */
426
      if (is_array($this->attrs[$index]) &&
427
428
          count ($this->attrs[$index]) == 1 &&
          isset($this->saved_attributes[$index]) &&
429
430
          !is_array($this->saved_attributes[$index])) {
        $this->attrs[$index] = $this->attrs[$index][0];
431
432
433
434
435
      }

      /* Remove emtpy arrays if they do not differ */
      if (is_array($this->attrs[$index]) &&
          count($this->attrs[$index]) == 0 &&
436
          !isset($this->saved_attributes[$index])) {
437
438
439
440
441
442
443
444
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove single attributes that do not differ */
      if (!is_array($this->attrs[$index]) &&
          isset($this->saved_attributes[$index]) &&
          !is_array($this->saved_attributes[$index]) &&
445
          $this->attrs[$index] == $this->saved_attributes[$index]) {
446
447
448
449
450
        unset ($this->attrs[$index]);
        continue;
      }

      /* Remove arrays that do not differ */
451
      if (is_array($this->attrs[$index]) &&
452
          isset($this->saved_attributes[$index]) &&
453
454
          is_array($this->saved_attributes[$index])) {
        if (!array_differs($this->attrs[$index], $this->saved_attributes[$index])) {
455
456
457
458
459
460
461
          unset ($this->attrs[$index]);
          continue;
        }
      }
    }

    /* Update saved attributes and ensure that next cleanups will be successful too */
462
    foreach ($this->attrs as $name => $value) {
463
464
465
466
      $this->saved_attributes[$name] = $value;
    }
  }

467
  /*!
468
   * \brief Check formular input
Benoit Mortier's avatar
Benoit Mortier committed
469
   */
470
471
  function check()
  {
472
    $message = array();
473
474

    /* Skip if we've no config object */
475
    if (!isset($this->config) || !is_object($this->config)) {
476
477
478
      return $message;
    }

479
480
481
    self::callHook($this, 'CHECK', array(), $returnOutput);
    if (!empty($returnOutput)) {
      $message[] = join("\n", $returnOutput);
482
483
484
    }

    /* Check entryCSN */
485
    if ($this->CSN_check_active) {
486
      $current_csn = getEntryCSN($this->dn);
487
      if (($current_csn != $this->entryCSN) && !empty($this->entryCSN) && !empty($current_csn)) {
488
        $this->entryCSN = $current_csn;
Benoit Mortier's avatar
Benoit Mortier committed
489
        $message[] = _("The object has changed since opened in FusionDirectory. All changes that may be done by others get lost if you save this entry!");
490
491
      }
    }
492
    return $message;
493
494
  }

495
496
497
  /*
   * \brief Adapt from template, using 'dn'
   *
498
   * \param string $dn The DN
499
500
   *
   * \param array $skip A new array
501
   */
502
  function adapt_from_template($dn, $skip = array())
503
504
  {
    /* Include global link_info */
505
    $ldap = $this->config->get_ldap_link();
506
507

    /* Load requested 'dn' to 'attrs' */
508
509
    $ldap->cat($dn);
    $this->attrs = $ldap->fetch();
510
511

    /* Walk through attributes */
512
    foreach ($this->attributes as $val) {
513
514

      /* Skip the ones in skip list */
515
      if (in_array($val, $skip)) {
516
517
518
        continue;
      }

519
      if (isset($this->attrs["$val"][0])) {
520

521
        /* If attribute is set, replace dynamic parts:
522
           %sn, %givenName and %uid. Fill these in our local variables. */
523
        $value = $this->tpl_parse($this->attrs["$val"][0]);
524

525
        $this->$val = $value;
526
527
528
529
      }
    }

    /* Is Account? */
530
    $this->is_account = $this->is_this_account($this->attrs);
531
532
  }

533
534
535
  /* Apply a modifier
   * Returns an array of possible values */
  static function tpl_apply_modifier($m, $args, $str)
536
  {
537
    switch ($m) {
538
539
540
541
542
      case 'u': // uppercase
        return array(mb_strtoupper($str, 'UTF-8'));
      case 'l': // lowercase
        return array(mb_strtolower($str, 'UTF-8'));
      case 'a': // remove accent
543
544
545
546
        $str = htmlentities($str, ENT_NOQUOTES, 'UTF-8');

        $str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str);
        $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // handle ligatures
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
        return array(preg_replace('#&[^;]+;#', '', $str)); // delete unhandled characters
      case 's': // substring
        if (count($args) < 1) {
          trigger_error("Missing 's' substr modifier parameter");
        }
        if (count($args) < 2) {
          array_unshift($args, 0);
        }
        if (preg_match('/^(\d+)-(\d+)$/', $args[1], $m)) {
          $res = array();
          for ($i = $m[1];$i < $m[2]; ++$i) {
            $res[] = substr($str, $args[0], $i);
          }
          return array_unique($res);
        } else {
          return array(substr($str, $args[0], $args[1]));
        }
564
565
      default:
        trigger_error("Unkown modifier '$m'");
566
        return array($str);
567
568
569
    }
  }

570
  static function tpl_parse_mask($mask, $objects)
571
572
  {
    if ($mask == "|") {
573
      return array("%");
574
    }
575
    $modifiers = '';
576
    if (preg_match('/^([^|]+)\|/', $mask, $m)) {
577
      $modifiers = $m[1];
578
579
      $mask = substr($mask, strlen($m[0]));
    }
580
581
582
583
584
585
586
    $result = NULL;
    foreach ($objects as &$object) {
      if (isset($object->$mask)) {
        $result = array($object->$mask);
        break;
      }
    }
587
    unset($object);
588
589
590
    if ($result === NULL) {
      trigger_error("'$mask' was not found in objects");
      $result = array('');
591
    }
592
    $len    = strlen($modifiers);
593
    for ($i = 0; $i < $len; ++$i) {
594
595
      $args     = array();
      $modifier = $modifiers[$i];
596
      if (preg_match('/^\[([^\]]+)\].*$/', substr($modifiers, $i + 1), $m)) {
597
598
        /* get modifier args */
        $args = explode(',', $m[1]);
599
        $i += strlen($m[1]) + 2;
600
601
602
603
604
605
      }
      $result_tmp = array();
      foreach ($result as $r) {
        $result_tmp = array_merge($result_tmp, self::tpl_apply_modifier($modifier, $args, $r));
      }
      $result = $result_tmp;
606
607
608
609
    }
    return $result;
  }

610
  function tpl_parse($string)
611
612
613
  {
    $offset = 0;
    while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
614
615
      $replace = self::tpl_parse_mask($m[1][0], array($this->parent, $this));
      $replace = $replace[0];
616
617
618
619
620
621
      $string = substr_replace($string, $replace, $m[0][1], strlen($m[0][0]));
      $offset = $m[0][1] + strlen($replace);
    }
    return $string;
  }

622
  /*
623
   * \brief Indicate whether a password change is needed or not
Benoit Mortier's avatar
Benoit Mortier committed
624
   */
625
626
627
628
629
  function password_change_needed()
  {
    return FALSE;
  }

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
  /*! Brief Parse attrs template masks
   *
   * return an array with the final values of attributes
   */
  static function tpl_parse_attrs($attrs)
  {
    foreach ($attrs as &$attr) {
      foreach ($attr as $key => &$string) {
        if (!is_numeric($key)) {
          continue;
        }
        $string = self::tpl_parse_string($string, $attrs);
      }
      unset($string);
    }
    unset($attr);
    return $attrs;
  }

  /*! Brief Parse template masks in a single string
   *
   * return the string with patterns replaced by their values
   */
  static function tpl_parse_string($string, $attrs)
  {
    $offset = 0;
    while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) {
      $replace  = self::tpl_parse_mask($m[1][0], $attrs);
      $replace  = $replace[0];
      $string   = substr_replace($string, $replace, $m[0][1], strlen($m[0][0]));
      $offset   = $m[0][1] + strlen($replace);
    }
    return $string;
  }
664

665
666
  /*!
   * \brief Show header message for tab dialogs
667
   *
668
   * \param string $button_text The button text
669
   *
670
   * \param string $text The text
671
   *
672
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
673
   */
674
  function show_enable_header($button_text, $text, $disabled = FALSE, $name = "modify_state")
675
  {
676
    return $this->show_header($button_text, $text, FALSE, $disabled, $name);
677
678
679
  }


680
  /*!
681
   * \brief Show header message for tab dialogs
682
   *
683
   * \param string $button_text The button text
684
   *
685
   * \param string $text The text
686
   *
687
   * \param boolean $disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
688
   */
689
  function show_disable_header($button_text, $text, $disabled = FALSE, $name = "modify_state")
690
  {
691
    return $this->show_header($button_text, $text, TRUE, $disabled, $name);
692
693
694
  }


695
  /*!
696
   * \brief Show header message for tab dialogs
697
   *
698
   * \param string $button_text The button text
699
   *
700
   * \param string $text The text
701
   *
702
   * \param boolean $plugin_enabled
703
   *
704
   * \param boolean $button_disabled FALSE
Benoit Mortier's avatar
Benoit Mortier committed
705
   */
706
  function show_header($button_text, $text, $plugin_enabled, $button_disabled = FALSE, $name = "modify_state")
707
  {
708
    if (($button_disabled) || ((!$this->acl_is_createable() && !$plugin_enabled) || (!$this->acl_is_removeable() && $plugin_enabled))) {
709
        $state = "disabled=\"disabled\"";
710
    } else {
711
        $state = "";
712
    }
713
714
    $display = "<div width=\"100%\"><p><b>$text</b><br/>\n";
    $display .= "<input type=\"submit\" value=\"$button_text\" name=\"$name\" ".$state.
715
      "></p></div><hr class=\"separator\"/>";
716

717
    return $display;
718
719
  }

720
721
722
723
724
725
726
  /*!
   * \brief Executes a command after an object has been copied
   */
  function postCopyHook()
  {
  }

727
  /*!
728
729
730
   * \brief Create unique DN
   *
   * \param string $data
731
   *
732
733
   * \param string $base
   */
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
  function create_unique_dn2($data, $base)
  {
    $ldap= $this->config->get_ldap_link();
    $base= preg_replace("/^,*/", "", $base);

    /* Try to use plain entry first */
    $dn= "$data,$base";
    $attribute= preg_replace('/=.*$/', '', $data);
    $ldap->cat ($dn, array('dn'));
    if (!$ldap->fetch()){
      return ($dn);
    }

    /* Look for additional attributes */
    foreach ($this->attributes as $attr){
      if ($attr == $attribute || $this->$attr == ""){
        continue;
      }

      $dn= "$data+$attr=".$this->$attr.",$base";
      $ldap->cat ($dn, array('dn'));
      if (!$ldap->fetch()){
        return ($dn);
      }
    }

    /* None found */
    return ("none");
  }


765
  /*!
766
767
768
   * \brief Create unique DN
   *
   * \param string $attribute
769
   *
770
771
   * \param string $base
   */
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
  function create_unique_dn($attribute, $base)
  {
    $ldap= $this->config->get_ldap_link();
    $base= preg_replace("/^,*/", "", $base);

    /* Try to use plain entry first */
    $dn= "$attribute=".$this->$attribute.",$base";
    $ldap->cat ($dn, array('dn'));
    if (!$ldap->fetch()){
      return ($dn);
    }

    /* Look for additional attributes */
    foreach ($this->attributes as $attr){
      if ($attr == $attribute || $this->$attr == ""){
        continue;
      }

      $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
      $ldap->cat ($dn, array('dn'));
      if (!$ldap->fetch()){
        return ($dn);
      }
    }

    /* None found */
    return ("none");
  }

801
802
803
  /*!
   * \brief ldap rebind
   *
804
   * \param string $ldap
805
   *
806
807
   * \param string $referral
   */
808
809
  function rebind($ldap, $referral)
  {
810
    $credentials = LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
811
    if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
812
813
814
815
      $this->error      = "Success";
      $this->hascon     = TRUE;
      $this->reconnect  = TRUE;
      return 0;
816
817
818
819
820
821
    } else {
      $this->error = "Could not bind to " . $credentials['ADMIN'];
      return NULL;
    }
  }

822
  /*
823
   * \brief Recursively copy ldap object
824
   *
825
   * \param string $src_dn The DN source
826
   *
827
828
   * \param string $dst_dn The DN destination
   */
829
  function _copy($src_dn, $dst_dn)
830
  {
831
    $ldap = $this->config->get_ldap_link();
832
    $ldap->cat($src_dn);
833
    $attrs = $ldap->fetch();
834
835

    /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
836
    $ds = ldap_connect($this->config->current['SERVER']);
837
838
839
840
841
    ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
    if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
      ldap_set_rebind_proc($ds, array(&$this, "rebind"));
    }

842
    $pwd  = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
843
    ldap_bind($ds, $this->config->current['ADMINDN'], $pwd);
844
845

    /* Fill data from LDAP */
846
847
848
    $new = array();
    if ($sr = ldap_read($ds, LDAP::fix($src_dn), "objectClass=*")) {
      if ($ei = ldap_first_entry($ds, $sr)) {
849
        foreach (array_keys($attrs) as $attr) {
850
851
852
853
          if ($info = @ldap_get_values_len($ds, $ei, $attr)) {
            for ($i = 0; $i < $info['count']; $i++) {
              if ($info['count'] == 1) {
                $new[$attr] = $info[$i];
854
              } else {
855
                $new[$attr][] = $info[$i];
856
857
858
859
860
861
862
              }
            }
          }
        }
      }
    }

Benoit Mortier's avatar
Benoit Mortier committed
863
    /* close connexion */
864
865
866
    ldap_unbind($ds);

    /* Adapt naming attribute */
867
868
869
    $dst_name       = preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
    $dst_val        = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
    $new[$dst_name] = LDAP::fix($dst_val);
870
871

    /* Check if this is a department.
872
     * If it is a dep. && there is a , override in his ou
873
874
     *  change \2C to , again, else this entry can't be saved ...
     */
875
876
    if (isset($new['ou']) && preg_match("/\\,/", $new['ou'])) {
      $new['ou'] = str_replace("\\\\,", ",", $new['ou']);
877
878
879
880
881
    }

    /* Save copy */
    $ldap->connect();
    $ldap->cd($this->config->current['BASE']);
882

883
884
    $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));

885
886
    /* FAIvariable=.../..., cn=..
        could not be saved, because the attribute FAIvariable was different to
887
888
        the dn FAIvariable=..., cn=... */

889
890
891
    if (!is_array($new['objectClass'])) {
      $new['objectClass'] = array($new['objectClass']);
    }
892

893
    if (in_array_ics("FAIdebconfInfo", $new['objectClass'])) {
894
895
896
897
898
      $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
    }
    $ldap->cd($dst_dn);
    $ldap->add($new);

899
    if (!$ldap->success()) {
900
901
      trigger_error("Trying to save $dst_dn failed.",
          E_USER_WARNING);
902
      return FALSE;
903
    }
904
    return TRUE;
905
906
  }

907
  /*
908
909
   * \brief Copy ldap object.
   * This is a workaround function
910
   *
911
   * \param string $src_dn The DN source
912
   *
913
914
   * \param string $dst_dn The DN destination
   */
915
916
917
  function copy($src_dn, $dst_dn)
  {
    /* Rename dn in possible object groups */
918
    $ldap = $this->config->get_ldap_link();
919
920
    $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
        array('cn'));
921
922
    while ($attrs = $ldap->fetch()) {
      $og = new ogroup($this->config, $ldap->getDN());
923
      unset($og->member[$src_dn]);
924
925
      $og->member[$dst_dn] = $dst_dn;
      $og->save();
926
927
928
    }

    $ldap->cat($dst_dn);
929
930
    $attrs = $ldap->fetch();
    if (count($attrs)) {
931
932
      trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
          E_USER_WARNING);
933
      return FALSE;
934
935
936
    }

    $ldap->cat($src_dn);
937
938
    $attrs = $ldap->fetch();
    if (!count($attrs)) {
939
940
      trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
          E_USER_WARNING);
941
      return FALSE;
942
943
944
    }

    $ldap->cd($src_dn);
945
946
    $ldap->search("objectClass=*", array("dn"));
    while ($attrs = $ldap->fetch()) {
947
      $src = $attrs['dn'];
948
949
      $dst = preg_replace("/".preg_quote($src_dn, '/')."$/", $dst_dn, $attrs['dn']);
      $this->_copy($src, $dst);
950
    }
951
    return TRUE;
952
953
954
  }


955
  /*!
956
   * \brief  Rename/Move a given src_dn to the given dest_dn
957
958
959
960
961
   *
   * Move a given ldap object indentified by $src_dn to the
   * given destination $dst_dn
   *
   * - Ensure that all references are updated (ogroups)
962
   * - Update ACLs
963
964
   * - Update accessTo
   *
965
   * \param  string  $src_dn the source DN.
966
   *
967
   * \param  string  $dst_dn the destination DN.
968
   *
969
970
   * \return boolean TRUE on success else FALSE.
   */
971
  private function rename($src_dn, $dst_dn)
972
973
974
975
  {
    /* Try to move the source entry to the destination position */
    $ldap = $this->config->get_ldap_link();
    $ldap->cd($this->config->current['BASE']);
976
977
978
979
980
    $ldap->create_missing_trees(preg_replace("/^[^,]+,/", "", $dst_dn));
    if (!$ldap->rename_dn($src_dn, $dst_dn)) {
      new log("debug", "Ldap Protocol v3 implementation error, ldap_rename failed, falling back to manual copy.",
              "FROM: $src_dn  -- TO: $dst_dn", array(), $ldap->get_error());
      @DEBUG(DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, "Rename failed FROM: $src_dn  -- TO:  $dst_dn",
981
          "Ldap Protocol v3 implementation error, falling back to maunal method.");
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
      return FALSE;
    }

    return TRUE;
  }


   /*!
    * \brief Move ldap entries from one place to another
    *
    * \param  string  $src_dn the source DN.
    *
    * \param  string  $dst_dn the destination DN.
    */
  function move($src_dn, $dst_dn)
  {
    /* Do not move if only upper- lowercase has changed */
    if (strtolower($src_dn) == strtolower($dst_dn)) {
      return TRUE;
    }

    /* Try to move with ldap routines, if this was not successfull
        fall back to the old style copy & remove method
     */
    if (!$this->rename($src_dn, $dst_dn)) {
      /* Copy source to destination */
      if (!$this->copy($src_dn, $dst_dn)) {
        return FALSE;
      }

      /* Delete source */
      $ldap = $this->config->get_ldap_link();
      $ldap->rmdir_recursive($src_dn);
      if (!$ldap->success()) {
        trigger_error("Trying to delete $src_dn failed.", E_USER_WARNING);
        return FALSE;
      }
1019
1020
1021
1022
1023
    }

    /* Get list of users,groups and roles within this tree,
        maybe we have to update ACL references.
     */
1024
1025
1026
    $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))", array("all"), $dst_dn,
                          array("dn","objectClass"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
    foreach ($leaf_objs as $obj) {
1027
      $new_dn = $obj['dn'];
1028
1029
      $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i", $src_dn, LDAP::convert($new_dn));
      $this->update_acls($old_dn, $new_dn);
1030
1031
1032
1033
    }

    // Migrate objectgroups if needed
    $ogroups = get_sub_list("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))",
1034
1035
                            "ogroup", array(get_ou("ogroupRDN")), $this->config->current['BASE'],
                            array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
1036
1037

    // Walk through all objectGroups
1038
    foreach ($ogroups as $ogroup) {
1039
      // Migrate old to new dn
1040
      $o_ogroup = new ogroup($this->config, $ogroup['dn']);
1041
1042
1043
      if (isset($o_ogroup->member[$src_dn])) {
        unset($o_ogroup->member[$src_dn]);
      }
1044
      $o_ogroup->member[$dst_dn] = $dst_dn;
1045

1046
1047
1048
1049
      // Save object group
      $o_ogroup->save();
    }

1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
    if (class_available('roleGeneric')) {
      /* Update roles to use the new entry dn */
      $roles = get_sub_list("(&(objectClass=organizationalRole)(roleOccupant=".LDAP::prepare4filter(LDAP::fix($src_dn))."))", "roles", array(get_ou("roleRDN")), $this->config->current['BASE'], array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);

      // Walk through all roles
      foreach ($roles as $role) {
        $role = new roleGeneric($this->config, $role['dn']);
        $key  = array_search($src_dn, $role->roleOccupant);
        if ($key !== FALSE) {
          $role->roleOccupant[$key] = $dst_dn;
          $role->save();
        }
1062
1063
1064
      }
    }

1065
    // Update 'manager' attributes from gosaDepartment and inetOrgPerson
1066
1067
    $filter     = '(&(objectClass=inetOrgPerson)(manager='.LDAP::prepare4filter(LDAP::fix($src_dn)).'))';
    $filter     = '(|'.$filter.'(&(objectClass=gosaDepartment)(manager='.LDAP::prepare4filter(LDAP::fix($src_dn)).')))';
1068
    $leaf_deps  = get_list($filter, array('all'), $this->config->current['BASE'],
1069
1070
                            array('manager','dn','objectClass'), GL_SUBSEARCH | GL_NO_ACL_CHECK);
    foreach ($leaf_deps as $entry) {