1 <?php
3 class servDNSeditZoneEntries extends plugin
4 {
5 /* CLI vars */
6 var $cli_summary= "Manage server basic objects";
7 var $cli_description= "Some longer text\nfor help";
8 var $cli_parameters= array("eins" => "Eins ist toll", "zwei" => "Zwei ist noch besser");
10 /* attribute list for save action */
11 var $ignore_account= TRUE;
12 var $attributes= array("zoneName");
13 var $objectclasses= array("whatever");
15 var $Devices = array();
16 var $InitDevices = array();
18 var $FreeHosts = array();
19 var $InitFreeHosts= array();
21 var $zoneName = ""; // ZoneName of currently edited Zone
22 var $reverseName = ""; // ReverseZone of the currently edited Zone
24 var $RecordTypes= array(); // Possible record type. They will be set in contrucktor
25 // and some types will be added later (cName/Ptr)
27 function servDNSeditZoneEntries ($config, $dn= NULL,$zoneName,$reverseName,$RTypes)
28 {
29 plugin::plugin ($config, $dn);
31 /* Initialise class
32 */
33 $this->zoneName = $zoneName;
34 $this->reverseName = $reverseName;
35 $this->RecordTypes = $RTypes;
37 /* Add records types which are not allowed in zones
38 */
39 $this->RecordTypes['cNAMERecord'] = "relativeDomainName";
40 $this->RecordTypes['pTRRecord'] = "relativeDomainName";
41 $this->RecordTypes['tXTRecord'] = "tXTRecord";
43 /* Get ldap connection
44 */
45 $ldap = $this->config->get_ldap_link();
46 $ldap->cd($this->config->current['BASE']);
48 /* Get all objects using this zone
49 * Workstations / Terminals
50 */
51 $res = $ldap->search("(&(objectClass=dNSZone)(|(zoneName=".$zoneName.")(zoneName=".$reverseName.".in-addr.arpa))(!(relativeDomainName=@)))");
52 while($attrs = $ldap->fetch($res)){
53 if(preg_match("/zoneName=/",$attrs['dn'])) continue;
54 foreach($this->RecordTypes as $atr => $val){
55 if((isset($attrs[$atr])) && (is_array($attrs[$atr]))){
56 unset($attrs[$val]['count']);
57 $dn = preg_replace("/^.*,cn/","cn",$attrs['dn']);
58 $this->Devices[$dn]['RECORDS'][$atr] = $attrs[$val];
59 $this->Devices[$dn]['Type'] = "Exists";
60 }
61 }
62 }
64 /* Add additional informations
65 to all catched objects ( ObjectInformations)
66 */
67 foreach($this->Devices as $dn => $Recs){
68 $attrs = $ldap->fetch($ldap->cat($dn,array("objectClass","cn")));
69 $this->Devices[$dn]['OBJECT'] = $attrs;
70 $this->Devices[$dn]['OrigCn'] = $attrs['cn'][0];
71 }
73 /* Add Free Hand entries
74 * Free hand entries are entries which are not directly assigned
75 * to an existing network object like a WS or a Terminal.
76 * They will be found in a subtree within this zone.
77 * For example :
78 * The object name is FreeHand1,
79 * zoneName=test.de,cn=server ...
80 * // ObjectContainer
81 * relativeDomainName=FreeHand1,zoneName=....
82 * // Dns entry
83 * relativeDomainName=FreeHand1, relativeDomainName=FreeHand1,zoneName=....
84 * // cName entry ....
85 * relativeDomainName=terminal12, relativeDomainName=FreeHand1,zone...
86 */
87 $ldap->cd ("zoneName=".$this->zoneName.",".$this->dn);
88 $ldap->ls ("(&(objectClass=dNSZone)(!(relativeDomainName=@)))","zoneName=".$this->zoneName.",".$this->dn,array("*"));
90 $GetSubInformations = array();
91 while($attrs = $ldap->fetch()){
93 unset($attrs[$val]['count']);
95 $dn = $attrs['dn'];
97 /* Split dn into relativeDomainNames
98 */
99 $tmp = split("relativeDomainName=",trim(preg_replace("/,zoneName=.*$/","\\1",$dn)));
100 $tmp2 = array();
101 foreach($tmp as $t){
102 if(empty($t)) continue;
103 $tmp2[] = preg_replace("/,$/","",$t);
104 }
106 /* Set object Name
107 */
108 $cn = $tmp2[0];
109 $dn = "relativeDomainName=".$cn.",zoneName=".$this->zoneName.",".$this->dn;
111 /* Create entry
112 */
113 $this->Devices[$dn]['OBJECT'] = $attrs;
114 $this->Devices[$dn]['OBJECT']['cn'][0] = $cn;//attrs['relativeDomainName'];
115 $this->Devices[$dn]['OBJECT']['OrigCn'] = $cn;//attrs['relativeDomainName'][0];
116 $this->Devices[$dn]['Type'] = "Free";
118 $GetSubInformations [] = $dn;
119 }
121 /* Get sub informations
122 CName or pTR records are stored in a sub entry.
124 // The base entry with aRecords/tXTrecords/....
125 relativeDomainName=Keks, relativeDomainName=Keks, zoneName=....
127 // And the sub entries like this one
128 relativeDomainName=10.2.64.in-addr.arpa, relativeDomainName=Keks, zoneName =...
129 ( They can't be stored in a single entry, so i decided to store them this way ).
130 */
131 foreach($GetSubInformations as $dn){
132 $ldap->ls("(&(objectClass=dNSZone)(!(relativeDomainName=@)))",$dn,array("*"));
133 while($attrs= $ldap->fetch()){
134 foreach($this->RecordTypes as $atr => $val){
135 if((isset($attrs[$atr])) && (is_array($attrs[$atr]))){
136 unset($attrs[$val]['count']);
137 $this->Devices[$dn]['RECORDS'][$atr] = $attrs[$val];
138 }
139 }
140 }
141 }
143 $this->Devices;
144 $this->InitDevices = $this->Devices;
145 }
147 function execute()
148 {
149 plugin::execute();
151 /* Check posts for operations ...
152 */
153 $once = true;
154 foreach($_POST as $name => $value){
156 /* Add a new Record in given object
157 */
158 if((preg_match("/^AddRecord_/",$name)) && ($once)){
159 $once = false;
160 $tmp = preg_replace("/^AddRecord_/","",$name);
161 $tmp = preg_replace("/_.*$/","",$tmp);
162 $tmp2 = split("\|",$tmp);
164 $dn = base64_decode($tmp2[0]);
165 $record = $tmp2[1];
166 $numrec = $tmp2[2];
168 $this->Devices[$dn]['RECORDS'][$record][] = "";
169 }
171 /* Remove record from given dn
172 */
173 if((preg_match("/^RemoveRecord_/",$name)) && ($once)){
174 $once = false;
175 $tmp = preg_replace("/^RemoveRecord_/","",$name);
176 $tmp = preg_replace("/_.*$/","",$tmp);
177 $tmp2 = split("\|",$tmp);
179 $dn = base64_decode($tmp2[0]);
180 $record = $tmp2[1];
181 $numrec = $tmp2[2];
183 unset($this->Devices[$dn]['RECORDS'][$record][$numrec]);
185 if(count($this->Devices[$dn]['RECORDS'][$record]) == 0){
186 unset($this->Devices[$dn]['RECORDS'][$record]);
187 if(count($this->Devices[$dn]['RECORDS']) ==0){
188 unset($this->Devices[$dn]);
189 }
190 }
192 }
194 /* Don't know how i should implement this ...
195 */
196 if((preg_match("/^UserRecord_/",$name)) && ($once)){
197 $once = false;
198 $tmp['RECORDS']['aRecord'][0] = 'empty';
199 $tmp['Type'] = 'Free';
200 $tmp['OBJECT']['cn'][0] = 'Free';
201 $this->Devices[] = $tmp;
202 }
203 }
205 /* Fill templating stuff */
206 $smarty= get_smarty();
207 $display= "";
209 $table = "";
210 foreach($this->Devices as $key => $dev){
211 $table .= $this->generateRecordConfigurationRow($key);
212 }
214 $smarty->assign("table",$table);;
215 $display.= $smarty->fetch(get_template_path('servDNSeditZoneEntries.tpl', TRUE));
216 return($display);
217 }
220 /* Create html table out of given entry
221 */
222 function generateRecordConfigurationRow($obj_dn){
224 /* get some basic inforamtions
225 */
226 $obj = $this->Devices[$obj_dn];
227 $objectName = $obj['OBJECT']['cn'][0];
229 /* Set title
230 */
231 if($obj['Type'] == "Free"){
232 $str = "<h3>".sprintf(_("Settings for host entry #%s"),($obj_dn +1))." : </h3>";
233 }else{
234 $str = "<h3>".sprintf(_("Settings for '%s'"),$obj_dn)." : </h3>";
235 }
237 $hostNameOnce = true;
238 /* Walk through all defined records
239 */
240 $str.= "<table cellspacing='0' cellpadding='0'>";
241 foreach($obj['RECORDS'] as $id => $types){
243 /* Create table
244 */
245 foreach($types as $key => $type){
247 /* Create unique post name
248 */
249 $name = base64_encode($obj_dn)."|".$id."|".$key;
251 $str .= "<tr>
252 <td style='width:80px;'>";
254 if($hostNameOnce){
255 $hostNameOnce = false;
256 $str .="<input type='text' name='RenameHost_".$name."' value='".$objectName."'>";
257 }else{
258 $str .=$objectName;
259 }
261 $str .="
262 </td>
263 <td style='width:80px;'>
264 ".$this->createRecordTypeSelection($id,$name)."
265 </td>
266 <td>
267 <input type='text' value='".$type."' name='ValueSelection_".$name."' style='width:250px;'>
268 </td>
269 <td style='width:30px;text-align:right;'>
270 <input type='image' name='AddRecord_".$name."' src='images/crossref.png' alt='"._("Add")."' title='"._("Add")."'>
271 </td>
272 <td style='width:60px;text-align:right;'>
273 <input type='image' name='UserRecord_".$name."' src='images/select_default.png' alt='"._("New")."' title='"._("New")."'>
274 <input type='image' name='RemoveRecord_".$name."' src='images/edittrash.png' alt='"._("Remove")."' title='"._("Remove")."'>
275 </td>
276 </tr>";
277 }
278 }
279 $str .="</table>";
280 return($str);
281 }
283 /* Create selectbox with all available option types
284 */
285 function createRecordTypeSelection($id,$refID,$isFreeRecord =false){
286 if(!$isFreeRecord){
287 $str = "\n<select name='RecordTypeSelection_".$refID."'>";
288 }else{
289 $str = "\n<select name='FreeRecordTypeSelection_".$refID."'>";
290 }
291 foreach($this->RecordTypes as $type => $atr) {
292 if($id == $type){
293 $str .="\n<option value='".$type."' selected >".strtoupper(preg_replace("/record/i","",$type))."</option>";
294 }else{
295 $str .="\n<option value='".$type."'>".strtoupper(preg_replace("/record/i","",$type))."</option>";
296 }
297 }
298 $str.= "\n</select>";
299 return($str);
300 }
302 function remove_from_parent()
303 {
304 }
306 function save_object()
307 {
308 /* Save all form fields
309 */
311 /* Possible attributes posted
312 */
313 $actions = array("RenameHost_","ValueSelection_");
314 foreach($_POST as $name=>$value){
316 foreach($actions as $act){
317 /* Check if attribute was posted
318 */
319 if(preg_match("/".$act."/",$name)){
321 /* Extract informations out of postname
322 */
323 $action = $act;
324 $tmp = preg_replace("/^".$action."/","",$name);
325 $tmp = preg_replace("/_.*$/","",$tmp);
326 $tmp2 = split("\|",$tmp);
328 $dn = base64_decode($tmp2[0]);
329 $record = $tmp2[1];
330 $numrec = $tmp2[2];
332 // entry value was psoted
333 if($act == "ValueSelection_"){
334 $this->Devices[$dn]['RECORDS'][$record][$numrec] = $value;
335 }elseif($act == "RenameHost_"){
336 $tmp = array();
337 foreach($this->Devices as $dnn => $dev){
338 if($dn == $dnn){
339 continue;
340 }
341 if(isset($dev['OrigCn'])){
342 $n1 = strtolower($dev['OrigCn']);
343 $tmp[$n1] = $n1;
344 }
345 $n2 = strtolower($dev['OBJECT']['cn'][0]);
346 $tmp[$n2] = $n2;
347 }
349 if(!isset($tmp[strtolower($value)])){
350 $this->Devices[$dn]['OBJECT']['cn'][0] = $value;
351 }
352 }
353 }
354 }
355 }
356 foreach($_POST as $name => $value){
358 /* Check if attribute was posted
359 */
360 if(preg_match("/^RecordTypeSelection_/",$name)){
362 /* Extract informations out of postname
363 */
364 $action = "RecordTypeSelection_";
365 $tmp = preg_replace("/^".$action."/","",$name);
366 $tmp = preg_replace("/_.*$/","",$tmp);
367 $tmp2 = split("\|",$tmp);
369 $dn = base64_decode($tmp2[0]);
370 $record = $tmp2[1];
371 $numrec = $tmp2[2];
373 if($record != $value){
374 $str = $this->Devices[$dn]['RECORDS'][$record][$numrec];
375 unset($this->Devices[$dn]['RECORDS'][$record][$numrec]);
376 $this->Devices[$dn]['RECORDS'][$value][] = $str;
377 }
378 }
379 }
380 }
382 /* check something
383 */
384 function check()
385 {
386 $message= array();
388 $ldap = $this->config->get_ldap_link();
389 $ldap->cd($this->config->current['BASE']);
391 foreach($this->Devices as $devDN => $device){
393 /* check: if given name for Free Hand entry is valid.
394 * cn exists ? invalid characters ? ...
395 */
396 if($device['Type'] == "Free"){
397 $cn = $device['OBJECT']['cn'][0];
399 $ldap->search("(cn=".$cn.")",array("cn"));
400 if($ldap->count()){
401 $message[] = sprintf(_("The name '%s' can't be used as host entry, the name is already used in this ldap tree."),$cn);
402 }
403 }
405 /* Checking entry name
406 */
407 if(!is_uid($device['OBJECT']['cn'][0])){
408 $message[] = sprintf(_("Entry name '%s' contains invalid characters."), $device['OBJECT']['cn'][0]);
410 }
412 /* Check if we have renamed the server, hich we are currently editing.
413 * reset the value and display a message.
414 */
415 if(strcmp($devDN,$this->dn) ==0 ){
416 $new = $device['OBJECT']['cn'][0];
417 $old = $this->InitDevices[$devDN]['OBJECT']['cn'][0];
419 if($new != $old){
420 $message[] = sprintf(_("Can't rename the server '%s' to '%s', the server contains this zone."),$old,$new);
421 }
422 }
425 /* Renaming check for existing devices
426 */
427 if(($device['Type'] == "Exists") && ($device['OrigCn'] != $device['OBJECT']['cn'][0])){
428 $cn = $device['OBJECT']['cn'][0];
430 $ldap->search("(cn=".$cn.")",array("cn"));
431 if($ldap->count()){
432 $message[] = sprintf(_("Can't rename '%s' to '%s',the destination name already exists."),$device['OrigCn'],$cn);
433 }
435 if(!is_uid($cn)){
436 $message[] = sprintf(_("Can't rename '%s' to '%s',the destination name contains invalid characters."),$device['OrigCn'],$cn);
437 }
438 }
440 /* Check records
441 */
442 $singleEntries = array("cNAMERecord","pTRRecord");
444 $tmp2 = array();
445 foreach($device['RECORDS'] as $type => $entries){
447 /* Check for multiple use of unique record types
448 */
449 if((in_array($type,$singleEntries)) && (count($entries) > 1)){
450 $message[] = sprintf(_("The record type '%s' is a unique type and can't be defined twice."),$type);
451 }
453 /* Check for empty / duplicate entries in record array
454 */
455 $tmp = array();
456 foreach($entries as $entry){
458 /* Check for empty records
459 */
460 if(empty($entry)){
461 $message[] = sprintf(_("There is an empty '%s' for host '%s'."),$type,$device['OBJECT']['cn'][0]);
462 }
464 /* Check for duplicate record entries
465 */
466 if(!isset($tmp[$entry])){
467 $tmp[$entry]= 'test';
468 }else{
469 $message[] = sprintf(_("There is a duplicate entry in '%s' for '%s'."),$type,$device['OBJECT']['cn'][0]);
470 }
471 }
472 }
473 }
474 return ($message);
475 }
477 function save()
478 {
479 /* Set all initial records to array()
480 This means they will be removed from the entry
481 if they won't be overwritten
482 */
483 foreach($this->InitDevices as $dn => $obj){
484 $attrs[$dn]= array();
485 foreach($obj['RECORDS'] as $rectype => $records){
486 $attrs[$dn][$rectype] = array();
487 }
488 }
490 /* Add new entries
491 */
492 foreach($this->Devices as $dn => $obj){
493 foreach($obj['RECORDS'] as $rectype => $records){
494 $attrs[$dn][$rectype] = array();
495 foreach($records as $rec){
496 $attrs[$dn][$rectype][] = $rec;
497 }
498 }
499 }
501 /* Get ldap link
502 */
503 $ldap = $this->config->get_ldap_link();
504 $ldap->cd ($this->config->current['BASE']);
506 /* Generate 'T odo' :-)
507 */
508 $tmp = ($this->generate_LDAP_entries($attrs));
510 /* Delete all objects which should be delted
511 */
512 foreach($tmp['del'] as $key => $ntr){
513 $ldap->rmdir_recursive($key);
514 }
516 /* Add append new / updated entries
517 */
518 foreach($tmp['addupdate'] as $key => $attrs){
519 $ldap->cat($key);
520 if($ldap->count() > 0){
521 $ldap->cd($key);
522 $ldap->modify($attrs);
523 }else{
524 $ldap->cd($this->config->current['BASE']);
525 $ldap->cd($key);
526 $ldap->add($attrs);
527 }
529 /* Display errors ...
530 */
531 if($ldap->error != "Success"){
532 echo "<br>".$ldap->error."<br>";
533 print_a($attrs);
534 echo $key;
535 }
536 }
538 /* Rename entries
539 !! Terminals / WS / etc will be renamed too
540 */
541 foreach($tmp['rename'] as $old => $new){
542 $ldap->cat($old);
543 $this->recursive_move($old,$new);
544 }
545 }
548 /* This function creates an array with following indexs
549 * 'delete' for all dns which should be deleted recursivly
550 * 'addupdated' Contains all entries which should be written
551 * 'rename' Contains all dns which should be renamed
552 */
553 function generate_LDAP_entries($objs)
554 {
556 $entries = array();
557 $delete = array();
558 $rename = array();
560 $ldap = $this->config->get_ldap_link();
561 $ldap->cd ($this->config->current['BASE']);
563 /* these entries are added to base object ...
564 */
565 $normal = array("mDRecord","aRecord","tXTRecord","mXRecord","hInfoRecord","mInfoRecord","aFSDBRecord","SigRecord","KeyRecord","aAAARecord","nSRecord",
566 "LocRecord","nXTRecord","sRVRecord","nAPTRRecord","kXRecord","certRecord","a6Record","dSRecord","sSHFPRecord","rRSIGRecord","nSECRecord");
568 /* Create t odo
569 */
570 foreach($objs as $dn => $recs){
572 /* Check if object must be renamed
573 */
574 if(isset($this->Devices[$dn]['OrigCn'])){
575 if($this->Devices[$dn]['OBJECT']['cn'][0] != $this->Devices[$dn]['OrigCn']){
576 $newdn = str_replace("cn=".$this->Devices[$dn]['OrigCn'], "cn=".$this->Devices[$dn]['OBJECT']['cn'][0],$this->Devices[$dn]['OBJECT']['dn']);
577 $rename[$this->Devices[$dn]['OBJECT']['dn']] = $newdn;
578 $rename["relativeDomainName=".$this->Devices[$dn]['OrigCn'].",".$newdn] = "relativeDomainName=".$this->Devices[$dn]['OBJECT']['cn'][0].",".$newdn;
579 }
580 }
582 /* Get basic info
583 */
584 if(isset($this->Devices[$dn])){
585 $obj = $this->Devices[$dn];
586 }else{
587 $obj = NULL;
588 }
590 /* If is valid entry
591 */
592 if($obj != NULL){
594 /* Both type must be handled different
595 * Free / Exists
596 */
597 if($obj["Type"] == "Free"){
598 $objectName = $obj['OBJECT']['cn'][0];
599 }else{
600 $objectName = $obj['OrigCn'];
601 }
604 /* If type is Free, check
605 */
606 if($obj['Type'] == "Free"){
608 /* Remove entry, if it was renamed
609 */
610 if(isset($obj['OBJECT']['OrigCn'])){
611 if($obj['OBJECT']['cn'][0] != $obj['OBJECT']['OrigCn']){
612 $delete["relativeDomainName=".$obj['OBJECT']['OrigCn'].",zoneName=".$this->zoneName.",".$this->dn]="";
613 }
614 }
616 /* Add new entry
617 */
618 $dn = "relativeDomainName=".$objectName.",zoneName=".$this->zoneName.",".$this->dn;
619 $dn2 = $dn;
621 $ldap->cat($dn);
622 if($ldap->count() == 0){
623 $entries[$dn]['objectClass'] = array("top","dNSZone");
624 $entries[$dn]['zoneName'] = $this->zoneName;
625 $entries[$dn]['relativeDomainName'] = $objectName;
626 }
627 }
628 }
630 /* Calculate records ...
631 */
632 foreach($recs as $type => $rec){
634 /* Simply add normal entries
635 */
636 if($obj != NULL){
637 if(in_array($type,$normal)){
638 $ldap->cat('relativeDomainName='.$objectName.','.$dn);
640 /* Append objectFrame if object doesn't exists in ldap
641 */
642 if($ldap->count() == 0) {
643 $entries['relativeDomainName='.$objectName.','.$dn]['objectClass'] = array("top","dNSZone");
644 $entries['relativeDomainName='.$objectName.','.$dn]['zoneName'] = $this->zoneName;
645 $entries['relativeDomainName='.$objectName.','.$dn]['relativeDomainName'] = $objectName;
646 }
647 $entries['relativeDomainName='.$objectName.','.$dn][$type] = $rec ;
648 }
650 /* Special handling for cNAMERecords
651 */
652 if($type == "cNAMERecord"){
654 $useDn = $dn;
655 if($obj['Type'] == "Free"){
656 $useDn = $dn2;
657 }
659 if(isset($this->InitDevices[$useDn]['RECORDS']['cNAMERecord'])){
660 foreach($this->InitDevices[$useDn]['RECORDS']['cNAMERecord'] as $warmal){
661 $delete['relativeDomainName='.$warmal.','.$useDn] = "";
662 }
663 }
664 foreach($rec as $r){
665 if(!empty($r)){
666 $entries['relativeDomainName='.$r.','.$useDn]['objectClass'] = array("top","dNSZone");
667 $entries['relativeDomainName='.$r.','.$useDn]['zoneName'] = $this->zoneName;
668 $entries['relativeDomainName='.$r.','.$useDn]['cNAMERecord'] = $objectName;
669 $entries['relativeDomainName='.$r.','.$useDn]['relativeDomainName'] = $r;
670 }
671 }
672 }
674 /* Special handling for ptrrecord
675 */
676 if($type == "pTRRecord"){
678 $useDn = $dn;
679 if($obj['Type'] == "Free"){
680 $useDn = $dn2;
681 }
683 if(isset($this->InitDevices[$dn]['RECORDS']['pTRRecord'])){
684 foreach($this->InitDevices[$dn]['RECORDS']['pTRRecord'] as $warmal){
685 $delete['relativeDomainName='.$warmal.','.$useDn] = "";
686 }
687 }
688 foreach($rec as $r){
689 if(!empty($r)){
690 $entries['relativeDomainName='.$r.','.$useDn]['pTRRecord'] = $objectName;
691 $entries['relativeDomainName='.$r.','.$useDn]['zoneName'] = $this->reverseName.".in-addr.arpa";
692 $entries['relativeDomainName='.$r.','.$useDn]['relativeDomainName'] = $r;
693 $entries['relativeDomainName='.$r.','.$useDn]['objectClass'] = array("top","dNSZone");
694 }
695 }
696 }
697 }
698 if($obj == NULL){
699 $obj = $this->InitDevices[$dn];
700 if($obj['Type'] == "Free"){
701 $delete['relativeDomainName='.$objectName.','.$dn] = "";
702 }else{
703 $ldap->cd($dn);
704 $ldap->ls("(&(objectClass=dNSZone)(!(relativeDomainName=@)))",$dn,array("relativeDomainName"));
705 while($attrs = $ldap->fetch()){
706 $delete[$attrs['dn']] = "";
707 }
708 }
709 }
711 }
712 }
713 $ret = array("del"=> $delete , "addupdate"=> $entries, "rename"=> $rename);
714 return($ret);
715 }
717 }
719 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
720 ?>