[gosa.git] / gosa-plugins / dns / admin / systems / services / dns / class_servDNSeditZoneEntries.inc
1 <?php
3 class servDNSeditZoneEntries extends plugin
4 {
5 /* attribute list for save action */
6 var $ignore_account = TRUE;
7 var $attributes = array();
8 var $objectclasses = array("whatever");
10 var $Devices = array();
12 var $zoneName = ""; // ZoneName of currently edited Zone
13 var $reverseName = ""; // ReverseZone of the currently edited Zone
15 var $RecordTypes = array(); // Possible record type.
16 var $acl = "";
17 var $disableDialog = false; // Dialog will be disabled, if this zone is new
20 function servDNSeditZoneEntries (&$config,$dn, &$zoneObject)
21 {
22 plugin::plugin ($config, $dn);
24 /* Initialise class
25 */
26 $this->RecordTypes = DNS::getDnsRecordTypes();
27 $this->dn = "zoneName=".$zoneObject['InitialzoneName'].",".$dn;
28 $this->zoneName = $zoneObject['InitialzoneName'];
29 $this->reverseName = $zoneObject['InitialReverseZone'];
31 /* Remove nSRecord from listed types */
32 if(isset($this->RecordTypes['nSRecord'])){
33 unset($this->RecordTypes['nSRecord']);
34 }
35 /* Remove nSRecord from listed types */
36 if(isset($this->RecordTypes['pTRRecord'])){
37 unset($this->RecordTypes['pTRRecord']);
38 }
40 /* Get ldap connection
41 */
42 $ldap = $this->config->get_ldap_link();
43 $ldap->cd($this->config->current['BASE']);
45 /* Get zone content
46 */
47 $ldap->ls("(&(objectClass=dNSZone)(!(relativeDomainName=@)))",$this->dn,array("relativeDomainName"));
49 while($attrs = $ldap->fetch()){
50 $this->Devices[$attrs['relativeDomainName'][0]] = DNS::getDNSHostEntries($config,$attrs['relativeDomainName'][0],true);
51 $this->Devices[$attrs['relativeDomainName'][0]]['OrigCn'] = $attrs['relativeDomainName'][0];
52 }
54 $ldap->cat($this->dn,array("objectClass"));
56 $this->disableDialog = true;
57 if(count($this->Devices)|| $ldap->count()){
58 $this->disableDialog = false;
59 }
60 }
62 function execute()
63 {
64 plugin::execute();
66 /* Fill templating stuff */
67 $smarty= get_smarty();
68 $display= "";
70 $table = "";
71 foreach($this->Devices as $key => $dev){
72 $table .= $this->generateRecordConfigurationRow($key);
73 }
75 $smarty->assign("acl",$this->acl);
76 $smarty->assign("disableDialog",$this->disableDialog);
77 $smarty->assign("table",$table);;
78 $display.= $smarty->fetch(get_template_path('servDNSeditZoneEntries.tpl', TRUE, dirname(__FILE__)));
79 return($display);
80 }
83 function save_object()
84 {
85 /* Check posts for operations ...
86 */
87 $once = true;
88 $ptr_updates = array();
89 if(!preg_match("/w/",$this->acl)) return;
91 foreach($_POST as $name => $value){
93 /* Extract informations out of post name
94 */
95 $tmp = preg_replace("/^[^_]*_/","\\1",$name);
96 $tmp2 = explode("|",postDecode($tmp));
98 /* Add new host entry
99 */
100 if((preg_match("/^UserRecord_?/",$name)) && ($once)){
101 $once = false;
102 $entry = DNS::getDNSHostEntries($this->config,"",true);
103 $entry['exists'] = true;
104 $entry['zoneName'] = strtoupper($this->attrs['cn'][0])."/".$this->zoneName;
105 $entry['RECORDS'][] = array("type" => "aRecord" , "value"=>"");
106 $this->Devices[_("New entry")] = $entry;
107 }
109 if(count($tmp2) != 2) continue;
111 $Name = $tmp2[0];
112 $RecordID = $tmp2[1];
114 /* Add new REcord
115 */
116 if((preg_match("/^AddRecord_/",$name)) && ($once)){
117 $once = false;
118 $this->Devices[$Name]['RECORDS'][] = $this->Devices[$Name]['RECORDS'][$RecordID];
119 }
121 /* Remove record from given dn
122 */
123 if((preg_match("/^RemoveRecord_/",$name)) && ($once)){
124 $once = false;
125 if(isset($this->Devices[$Name]['RECORDS'][$RecordID])){
126 unset($this->Devices[$Name]['RECORDS'][$RecordID]);
127 }
129 /* Check if there is at least one visible record. Else remove complete entry */
130 $visible = false;
131 foreach($this->Devices[$Name]['RECORDS'] as $rec){
132 if(in_array_strict($rec['type'],$this->RecordTypes)){
133 $visible = true;
134 break;
135 }
136 }
137 if(!$visible && isset($this->Devices[$Name]['RECORDS'])){
138 $this->Devices[$Name]['RECORDS'] = array();
139 }
140 }
141 }
143 /* Possible attributes posted
144 */
145 foreach($_POST as $name => $value){
147 $value = get_post($name);
149 /* Extract informations out of post name
150 */
151 $tmp = preg_replace("/^[^_]*_/","\\1",$name);
152 $tmp2 = explode("|",postDecode($tmp));
154 if(count($tmp2) != 2) continue;
156 $Name = $tmp2[0];
157 $RecordID = $tmp2[1];
159 /* Check for value change
160 */
161 if(preg_match("/ValueSelection_/",$name)){
162 if(isset($this->Devices[$Name]['RECORDS'][$RecordID])){
164 /* Update value */
165 $old = $this->Devices[$Name]['RECORDS'][$RecordID]['value'];
166 $this->Devices[$Name]['RECORDS'][$RecordID]['value'] = get_post($name);
168 /* Handle pTRRecord */
169 if(!isset($ptr_updates[$Name]) && $this->Devices[$Name]['RECORDS'][$RecordID]['type'] == "aRecord"){
171 $found = false;
172 $ip = $value;
173 $match = preg_replace("/^[^\/]*+\//","",$this->reverseName);
174 $ip = preg_replace("/^".preg_quote($match)."/","",$ip);
175 $ip = preg_replace("/^\./","",$ip);
177 foreach($this->Devices[$Name]['RECORDS'] as $key => $dev){
178 if($dev['type'] == "pTRRecord"){
179 $ptr_updates[$Name] = $Name;
180 $this->Devices[$Name]['RECORDS'][$key]['value'] = $ip;
181 $found = true;
182 break;
183 }
184 }
185 if(!$found){
186 $dev = array('type'=> 'pTRRecord', 'value' => $ip);
187 $this->Devices[$Name]['RECORDS'][] = $dev;
188 }
189 }
190 }
191 }
193 /* record type changed
194 */
195 if(preg_match("/^RecordTypeSelection_/",$name)){
196 if(isset($this->Devices[$Name]['RECORDS'][$RecordID])){
197 $this->Devices[$Name]['RECORDS'][$RecordID]['type'] = $value;
198 }
199 }
200 }
202 /* check for renamed entries
203 */
204 foreach($_POST as $name => $value){
207 $value = get_post($name);
209 /* Extract informations out of post name
210 */
211 $tmp = preg_replace("/^[^_]*_/","\\1",$name);
212 $tmp2 = explode("|",postDecode($tmp));
214 if(count($tmp2) != 2) continue;
216 $Name = $tmp2[0];
217 $RecordID = $tmp2[1];
219 /* Host renamed
220 */
221 if(preg_match("/RenameHost_/",$name)){
222 if((isset($this->Devices[$Name])) && ($Name != $value)){
224 if(isset($this->Devices[$value])){
225 msg_dialog::display(_("Error"), sprintf(_("Cannot rename '%s' to '%s'. Name is already in use!"), $Name, $value), ERROR_DIALOG);
226 }else{
227 $this->Devices[$value] = $this->Devices[$Name];
228 unset($this->Devices[$Name]);
229 }
230 }
231 }
232 }
233 }
236 /* check something
237 */
238 function check()
239 {
240 /* Call common method to give check the hook */
241 $message= plugin::check();
243 if(!preg_match("/w/",$this->acl)) return($message);
245 $ldap = $this->config->get_ldap_link();
246 $ldap->cd($this->config->current['BASE']);
248 $names = array();
250 foreach($this->Devices as $DevName => $device){
252 /* Don't need to check empty values ... */
253 if(!count($device['RECORDS'])) continue;
255 /* Checking entry name
256 */
257 if(!preg_match("/^[a-z0-9_\.-]+$/i", $DevName) || (empty($DevName))){
258 $message[] = msgPool::invalid(_("Name"),$DevName,"/[a-z0-9_\.-]/i");
259 }
261 /* Renaming check for existing devices
262 */
263 if(isset($device['OrigCn']) && ($DevName != $device['OrigCn'] )){
264 $ldap->cd($this->config->current['BASE']);
265 $ldap->search("(relativeDomainName=".$DevName.")",array("relativeDomainName"));
266 if($ldap->count()){
267 $message[] = sprintf(_("Cannot rename '%s' to '%s'. Entry is already in use."),$device['OrigCn'],$DevName);
268 }
269 }elseif(!isset($device['OrigCn'])){
270 $ldap->cd($this->config->current['BASE']);
271 $ldap->search("(relativeDomainName=".$DevName.")",array("relativeDomainName"));
272 if($ldap->count()){
273 $message[] = sprintf(_("Cannot create '%s'. Entry is already in use."),$DevName);
274 }
275 }
277 /* Check names
278 */
279 if(!isset($names[$DevName])){
280 $names[$DevName] = "";
281 }else{
282 $message[] = sprintf(_("Entry '%s' is used more than once."),$DevName);
283 }
285 /* Names should be written in lowercase
286 */
287 # if(strtolower($DevName) != $DevName){
288 # $message[] = sprintf(_("The host name '%s' should be written in lowercase."), $DevName);
289 # }
291 /* Check records
292 */
293 $singleEntries = array("pTRRecord");
295 $tmp = array();
296 $tmp2 = array();
297 foreach($device['RECORDS'] as $Num => $Rec){
299 /* Check values */
300 $message += $this->checkRecordType($DevName, $Rec['type'], $Rec['value']);
302 /* Check for multiple use of unique record types
303 */
304 if(in_array_strict($Rec['type'],$singleEntries)){
305 if(!isset($tmp[$Rec['type']])){
306 $tmp[$Rec['type']] = "";
307 }else{
308 $message[] = sprintf(_("%s records cannot be used more than once."),$Rec['type']);
309 }
310 }
312 /* Check for empty / duplicate entries in record array
313 */
314 if(empty($Rec['value'])){
315 $message[] = sprintf(_("Please fix the empty %s record for entry '%s'."),$Rec['type'],$DevName);
316 }
318 /* Check for duplicate record entries
319 */
320 if(!isset($tmp[$Rec['type']][$Rec['value']])){
321 $tmp[$Rec['type']][$Rec['value']] = "";
322 }else{
323 $message[] = sprintf(_("Please fix the duplicate %s record for entry '%s'."),$Rec['type'],$DevName);
324 }
325 }
326 }
327 return ($message);
328 }
331 function save()
332 {
333 if($this->disableDialog || !preg_match("/w/",$this->acl)) return;
335 $todo = array();
339 /* Create todolist
340 */
341 foreach($this->Devices as $name => $dev){
342 if(isset($dev['OrigCn'])){
343 if(count($dev['RECORDS'])){
344 $todo[] = DNS::getDNSHostEntriesDiff($this->config,$dev['OrigCn'],$dev,$name);
345 }else{
346 $dev['exists'] = false;
347 $todo[] = DNS::getDNSHostEntriesDiff($this->config,$dev['OrigCn'],$dev,$name);
348 }
349 }else{
350 if(count($dev['RECORDS'])){
351 $todo[] = DNS::getDNSHostEntriesDiff($this->config,"",$dev,$name);
352 }else{
353 $dev['exists'] = false;
354 $todo[] = DNS::getDNSHostEntriesDiff($this->config,"",$dev,$name);
355 }
356 }
357 }
359 $tmp = array();
360 $tmp['del'] = array();
361 $tmp['add'] = array();
362 $tmp['move'] = array();
363 foreach($todo as $to){
364 foreach($to as $type => $entries){
365 $tmp[$type] = array_merge($tmp[$type],$entries);
366 }
367 }
369 /* Get ldap link
370 */
371 $ldap = $this->config->get_ldap_link();
372 $ldap->cd ($this->config->current['BASE']);
374 /* move follwoing entries
375 */
376 foreach($tmp['move'] as $src => $dst){
377 $this->recursive_move($src,$dst);
378 }
380 /* Delete dns */
381 foreach($tmp['del'] as $dn => $del){
382 $ldap->cd($dn);
383 $ldap->rmdir_recursive($dn);
384 if(is_object($this->parent->parent)){
385 $this->parent->parent->handle_post_events("remove",array("dn" => $dn));
386 }
387 }
389 /* Add || Update new DNS entries
390 */
391 foreach($tmp['add'] as $dn => $attrs){
392 $ldap->cd($dn);
393 $ldap->cat($dn, array('dn'));
394 if(count($ldap->fetch())){
395 $ldap->cd($dn);
396 $ldap->modify ($attrs);
397 if(is_object($this->parent->parent)){
398 $this->parent->parent->handle_post_events("modify",array("dn" => $dn));
399 }
400 }else{
401 $ldap->cd($dn);
402 $ldap->add($attrs);
403 if(is_object($this->parent->parent)){
404 $this->parent->parent->handle_post_events("add",array("dn" => $dn));
405 }
406 }
407 }
408 }
411 /* Create html table out of given entry
412 */
413 function generateRecordConfigurationRow($objKey){
415 /* Get some basic informations
416 */
417 $obj = $this->Devices[$objKey];
418 $objectName = $objKey;
420 $dis = "";
421 if(!preg_match("/w/",$this->acl)){
422 $dis = " disabled ";
423 }
425 /* Abort if emtpy
426 */
427 if(count($obj['RECORDS']) == 0) return "";
429 /* Set title
430 */
431 $str= "<br>";
433 $hostNameOnce = true;
435 /* Walk through all defined records
436 */
437 $str.= "<table summary=''>";
438 foreach($obj['RECORDS'] as $id => $record){
440 /* Skip not selectable entries */
441 if(!isset($this->RecordTypes [$record['type']])) {
442 continue;
443 }
445 /* Create unique post name
446 */
447 $name = postEncode($objKey."|".$id);
449 /* Only first host entry name should be editable
450 */
451 if($hostNameOnce){
452 $hostNameOnce = false;
453 $field1 ="<input $dis type='text' style='width:250px;' name='RenameHost_".$name."' value='".set_post($objectName)."'>\n";
454 }else{
455 $field1 = "";
456 }
457 $field2 = $this->createRecordTypeSelection($record['type'],$name);
458 $field3 = "<input type='text' $dis value='".set_post($record['value'])."' name='ValueSelection_".$name."' style='width:250px;'>";
459 if(preg_match("/w/",$this->acl)){
460 $acl = image('images/lists/element.png[new]',"AddRecord_".$name,_("Add"));
461 $acl.= image('images/lists/trash.png',"RemoveRecord_".$name,_("Remove"));
462 }
464 /* Display settings backwards for CNAMERecords
465 */
466 if($record['type'] == "cNAMERecord"){
467 $str .= "
468 <tr>
469 <td style='width:250px;text-align:right;'>".$field3."</td>
470 <td style='width:90px;'>".$field2."</td>
471 <td>".$objectName."</td>
472 <td>".$acl."</td>
473 </tr>";
474 }else{
475 $str .= "
476 <tr>
477 <td style='width:75px;text-align:right;'>".$field1."</td>
478 <td style='width:90px;'>".$field2."</td>
479 <td>".$field3."</td>
480 <td>".$acl."</td>
481 </tr>";
482 }
483 }
484 $str .="</table>";
485 return($str);
486 }
489 /* Create selectbox with all available option types
490 */
491 function createRecordTypeSelection($id,$refID){
493 if(preg_match("/w/",$this->acl)){
494 $str = "\n<select name='RecordTypeSelection_".$refID."' onChange='document.mainform.submit();'>";
495 foreach($this->RecordTypes as $type => $atr) {
496 if($id == $type){
497 $str .="\n<option value='".$type."' selected >".strtoupper(preg_replace("/record/i","",$type))."</option>";
498 }else{
499 $str .="\n<option value='".$type."'>".strtoupper(preg_replace("/record/i","",$type))."</option>";
500 }
501 }
502 $str.= "\n</select>";
503 }else{
504 $str = " ".strtoupper(preg_replace("/record/i","",$id));
505 }
506 return($str);
507 }
510 /* Check record types for strange inputs */
511 function checkRecordType($name, $type, $value)
512 {
513 $template = _("The syntax of entry %s (record type %s, value %s) is invalid!")." %s<br><br><i>"._("Example").":</i> %s";
515 $message = Array();
516 switch($type) {
517 case 'aAAARecord': // RFC 3596
518 if(!tests::is_dns_name($name)) {
519 $message[] = sprintf($template, $name, $type, $value, _("Entry should be a DNS name."), "example");
520 }
521 if(!tests::is_ipv6($value)) {
522 $message[] = sprintf($template, $name, $type, $value, _("Value should be an IPv6 address."), "1fff:0000:0a88:85a3:0000:0000:ac1f:8001");
523 }
524 break;
525 case 'aRecord': // RFC 1035
526 if(!tests::is_dns_name($name)) {
527 $message[] = sprintf($template, $name, $type, $value, _("Entry should be a DNS name."), "example");
528 }
529 if(!tests::is_ip($value)) {
530 $message[] = sprintf($template, $name, $type, $value, _("Value should be an IPv4 address."), "192.168.1.10");
531 }
532 break;
533 case 'cNAMERecord': // RFC 1035
534 if(!tests::is_dns_name($name)) {
535 $message[] = sprintf($template, $name, $type, $value, _("Entry should be a DNS name."), "example");
536 }
537 if(!tests::is_dns_name($value)) {
538 $message[] = sprintf($template, $name, $type, $value, _("Value should be a DNS name."), "example");
539 }
540 break;
541 case 'mXRecord': // RFC 1035
542 //value: preference target
543 if(preg_match('/^(\S+)\s+(\S+)$/', $value, $matches)) {
544 if(!tests::is_id($matches[1])) {
545 $message[] = sprintf($template, $name, $type, $value, _("Value 1 should be a number."), "10 example");
546 }
547 if(!tests::is_dns_name($matches[2])) {
548 $message[] = sprintf($template, $name, $type, $value, _("Value 2 should be a DNS name."), "10 example");
549 }
550 } else {
551 $message[] = sprintf($template, $name, $type, $value, _("Value should be composed of 'preference target'."), "10 example");
552 }
553 break;
554 case 'nSRecord': // RFC 1035
555 if(!tests::is_dns_name($value)) {
556 $message[] = sprintf($template, $name, $type, $value, _("Value should be a DNS name."), "example");
557 }
558 break;
559 case 'pTRRecord': // RFC 1035
560 if(!tests::is_dns_name($value)) {
561 $message[] = sprintf($template, $name, $type, $value, _("Value should be a DNS name."), "example");
562 }
563 break;
564 case 'sRVRecord': // RFC 2782
565 if(!tests::is_dns_name($name)) {
566 $message[] = sprintf($template, $name, $type, $value, _("Entry should be a DNS name."), "example");
567 }
568 //value: priority weight port target
569 if(preg_match('/^([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+(\S+)$/', $value, $matches)) {
570 if(!tests::is_id($matches[1])) {
571 $message[] = sprintf($template, $name, $type, $value, _("Value 1 (priority) should be a number."), "0 5 5060 example");
572 }
573 if(!tests::is_id($matches[2])) {
574 $message[] = sprintf($template, $name, $type, $value, _("Value 2 (weight) should be a number."), "0 5 5060 example");
575 }
576 if(!tests::is_id($matches[3])) {
577 $message[] = sprintf($template, $name, $type, $value, _("Value 3 (port) should be a number."), "0 5 5060 example");
578 }
579 if(!tests::is_dns_name($matches[4])) {
580 $message[] = sprintf($template, $name, $type, $value, _("Value 4 (target) should be a DNS name."), "0 5 5060 example");
581 }
582 } else {
583 $message[] = sprintf($template, $name, $type, $value, _("Value should be composed of 'priority weight port target'."), "0 5 5060 example");
584 }
585 break;
586 }
587 return $message;
588 }
591 function remove_from_parent()
592 {
593 }
595 }
597 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
598 ?>