1 <?php
3 class DNS
4 {
5 static $RecordTypes= array('aRecord' => "aRecord",
6 'mDRecord' => "mDRecord",
7 'mXRecord' => "mXRecord",
8 'nSRecord' => "nSRecord",
9 'pTRRecord' => "relativeDomainName",
10 'hInfoRecord' => "hInfoRecord",
11 'mInfoRecord' => "mInfoRecord",
12 'cNAMERecord' => "relativeDomainName",
13 'tXTRecord' => "tXTRecord",
14 'aFSDBRecord' => "aFSDBRecord",
15 'SigRecord' => "SigRecord",
16 'KeyRecord' => "KeyRecord",
17 'aAAARecord' => "aAAARecord",
18 'LocRecord' => "LocRecord",
19 'nXTRecord' => "nXTRecord",
20 'sRVRecord' => "sRVRecord",
21 'nAPTRRecord' => "nAPTRRecord",
22 'kXRecord' => "kXRecord",
23 'certRecord' => "certRecord",
24 'a6Record' => "a6Record",
25 'dSRecord' => "dSRecord",
26 'sSHFPRecord' => "sSHFPRecord",
27 'rRSIGRecord' => "rRSIGRecord",
28 'nSECRecord' => "nSECRecord");
29 /* All available record types */
30 /* Return all record types
31 */
32 static function getDnsRecordTypes($ForZones = false)
33 {
34 if($ForZones){
35 $tmp = DNS::$RecordTypes;
36 unset($tmp['cNAMERecord']);
37 unset($tmp['pTRRecord']);
38 unset($tmp['tXTRecord']);
39 return($tmp);
40 }else{
41 return(DNS::$RecordTypes);
42 }
43 }
46 /* This fucntion is used to flip the ip address, for example
47 12.3.45 -> 45.3.12
48 Because some entries (like zones) are store like that 45.3.12.in-addr.arpa
49 but we want to display 12.3.45.
50 */
51 static function FlipIp($ip)
52 {
53 $tmp = array_reverse(split("\.",$ip));
54 $new = "";
55 foreach($tmp as $section){
56 $new .= $section.".";
57 }
58 return(preg_replace("/.$/","",$new));
59 }
62 /* This static function returns the zones specified for given host
63 */
64 static function getDNSZoneEntries($config,$HostDn,$silent = false)
65 {
66 $ldap = $config->get_ldap_link();
67 $ldap->cd($config->current['BASE']);
69 /* Not all records are allowed within a zone entry
70 */
71 $SkipRecords = array("tXTRecord","cNAMERecord","pTRRecord");
73 /* Special sOArecords
74 */
75 $sOAREcords = array("0"=>"sOAprimary","1"=>"sOAmail","2"=>"sOAserial","3"=>"sOArefresh","4"=>"sOAretry","5"=>"sOAexpire","6"=>"sOAttl");
77 /* Create tempalte for all fetched zone Data
78 */
79 $ZoneBase = array();
80 $ZoneBase['exists'] = false;
81 $ZoneBase['RECORDS'] = array();
82 $ZoneBase['zoneName'] = array();
83 $ZoneBase['dNSClass'] = array();
85 foreach($sOAREcords as $attr){
86 $ZoneBase[$attr] = "";
87 }
89 $Zones = array();
91 /* Get & Parse all zone entries
92 */
93 $ldap->ls("(&(objectClass=dNSZone)(zoneName=*)(relativeDomainName=@))",$HostDn,array("*"));
94 $tmp_res = array();
95 while($attrs = $ldap->fetch()) {
96 $tmp_res[] = $attrs;
97 }
99 /* Parse fetched zones
100 */
101 foreach($tmp_res as $attrs){
103 $zoneName = $attrs['zoneName'][0];
104 $Zones[$zoneName] = $ZoneBase;
105 $Zones[$zoneName]['exists'] = true;
107 /* Set basic attributes
108 */
109 foreach(array("zoneName","dNSClass") as $attr){
110 if(isset($attrs[$attr][0])){
111 $Zones[$zoneName][$attr] = $attrs[$attr][0];
112 }
113 }
115 /* Set initial zone name, to be able to detect if this entry was renamed
116 */
117 $Zones[$zoneName]['InitialzoneName'] = $zoneName;
119 /* Generate SOA entry
120 */
121 if(isset($attrs['sOARecord'][0])){
122 $tmp = split("\ ",$attrs['sOARecord'][0]) ;
123 $tmp2 = array();
125 /* Assign soa vars */
126 foreach($sOAREcords as $key => $name){
127 if(isset($tmp[$key])){
128 $Zones[$zoneName][$name] = $tmp[$key];
129 }else{
130 $Zones[$zoneName][$name] = "";
131 }
132 }
133 } // ENDE SOA Record
135 /* Get record attributes
136 */
137 foreach(DNS::$RecordTypes as $name => $value){
139 /* Skip some attributes
140 */
141 if(in_array($name,$SkipRecords)) continue;
143 /* If there is a record attribute
144 */
145 if(isset($attrs[$name])){
147 /* get all entries
148 */
149 for($i = 0 ; $i < $attrs[$value]['count']; $i ++){
150 $Zones[$zoneName]['RECORDS'][] = array("type"=>$name,"value"=>$attrs[$value][$i]);
151 }
152 }
153 }
155 /* Get reverse record ..
156 */
157 $ldap->ls("(&(objectClass=dNSZone)(relativeDomainName=@)(zoneName=*))",$attrs['dn'],array("zoneName"));
159 if($ldap->count() == 0){
160 if(!$silent){
161 msg_dialog::display(_("Error"), sprintf(_("Cannot find reverse zone for DNS zone '%s'. Parsing zone aborted."),$zoneName), ERROR_DIALOG);
162 }
163 unset($Zones[$zoneName]);
164 }elseif($ldap->count()>1){
165 if(!$silent){
166 msg_dialog::display(_("Error"), sprintf(_("Found more than one reverse zone for '%s'. Parsing zone aborted."),$zoneName), ERROR_DIALOG);
167 }
168 unset($Zones[$zoneName]);
169 }else{
170 $tmp = $ldap->fetch();
171 $Zones[$zoneName]['ReverseZone'] = DNS::FlipIp(str_replace(".in-addr.arpa","",$tmp['zoneName'][0]));
172 $Zones[$zoneName]['InitialReverseZone'] = DNS::FlipIp(str_replace(".in-addr.arpa","",$tmp['zoneName'][0]));
173 }
174 }
175 return($Zones);
176 }
179 /* This static function compares two dns zone objects and returns an
180 * array with following indexes
181 * - delete, for dns which must be deleted (only if dns zone is removed)
182 * - rename, if a dn must be renamed, for example, the zoneName has changed
183 * - add, if there is a new dns account created
184 */
185 static function getDNSZoneEntriesDiff($config,$newZones,$HostDn)
186 {
187 $oldZones = DNS::getDNSZoneEntries($config,$HostDn,true);
189 $sOAattributes = array("sOAprimary","sOAmail","sOAserial","sOArefresh","sOAretry","sOAexpire","sOAttl");
191 $move = array();
192 $add = array();
193 $del = array();
195 /* Generate a template for zones with default values
196 */
197 $zoneBase = array();
198 $zoneBase['objectClass'] = array("top","dNSZone");
199 $zoneBase['zoneName'] = "";
200 $zoneBase['relativeDomainName'] = "@";
201 $zoneBase['dNSClass'] = "IN";
202 $zoneBase['sOARecord'] = "";
204 /* Contains all renamed zoneNames
205 * For zone entry udpdates
206 */
207 $PrePareZoneEntries = array();
209 /* Walk through all zones and detect renamed/added/deleted zones ...
210 */
211 foreach($newZones as $name => $zone){
213 /* This zone was renamed
214 */
215 if((!empty($zone['InitialzoneName'])) && ($zone['InitialzoneName'] != $zone['zoneName'])){
217 /* Move old zone to new position
218 */
219 $oldDn = "zoneName=".$zone['InitialzoneName'].",".$HostDn;
220 $newDn = "zoneName=".$zone['zoneName'].",".$HostDn;
221 $PrePareZoneEntries[$zone['InitialzoneName']] = $zone['zoneName'];
222 $move [$oldDn] = $newDn;
223 }
225 /* Get old zone if available
226 */
227 $oldZone=array();
228 if(!empty($oldZones[$zone['InitialzoneName']])){
229 $oldZone = $oldZones[$zone['InitialzoneName']];
230 }
232 /* Create forward zone entry and put it in our add queue
233 */
234 $newDn = "zoneName=".$zone['zoneName'].",".$HostDn;
235 $obj = $zoneBase;
236 $obj['zoneName'] = $zone['zoneName'];
238 /* Create sOARecord & add it to the obj
239 */
240 $soa = "";
241 foreach($sOAattributes as $attr){
242 $soa.=" ".$zone[$attr];
243 }
244 $obj['sOARecord'] = trim($soa);
245 $obj['nSRecord'] = $zone['sOAprimary'];
247 /* If reverse zone was renamed, move entry
248 */
249 if(!empty($zone['InitialReverseZone'])){
250 if($zone['InitialReverseZone'] != $zone['ReverseZone']){
251 $base = "zoneName=".$zone['zoneName'].",".$HostDn;
252 $oldRDn = "zoneName=". DNS::FlipIp($zone['InitialReverseZone']).".in-addr.arpa,".$base;
253 $newRDn = "zoneName=". DNS::FlipIp($zone['ReverseZone']).".in-addr.arpa,".$base;
254 $PrePareZoneEntries[DNS::FlipIp($zone['InitialReverseZone']).".in-addr.arpa"] = DNS::FlipIp($zone['ReverseZone']).".in-addr.arpa";
255 $move [$oldRDn] = $newRDn;
256 }
257 }
259 /* Append record entries
260 * Set old value to array, to ensure that
261 * they will be deleted if necessary
262 */
263 if(isset($oldZone['RECORDS'])){
264 foreach($oldZone['RECORDS'] as $rec){
265 $obj[$rec['type']] = array();
266 }
267 }
269 /* Add new Records
270 */
271 foreach($zone['RECORDS'] as $rec){
272 if(!isset($obj[$rec['type']])||!is_array($obj[$rec['type']])){
273 $obj[$rec['type']] = array();
274 }
275 $obj[$rec['type']][] = $rec['value'];
276 }
278 /* Append udpated Zone Forward Entry to our add queue
279 */
280 $add[$newDn] = $obj;
282 /* Create Reverse Entry
283 * And append it to our add queue
284 */
285 $zone['ReverseZone'] = DNS::FlipIp($zone['ReverseZone']).".in-addr.arpa";
286 $base = "zoneName=".$zone['zoneName'].",".$HostDn;
287 $newRDn = "zoneName=".$zone['ReverseZone'].",".$base;
288 $rObj = $obj;
289 $rObj['zoneName']= $zone['ReverseZone'];
290 $add[$newRDn] = $rObj;
292 /* Remove currently managed zone from oldZones.
293 * this gives us the ability to detect removed zones
294 */
295 if(isset($oldZones[$zone['InitialzoneName']])){
296 unset($oldZones[$zone['InitialzoneName']]);
297 }
298 }
300 /* The rest of our oldZones must be deleted
301 * because they are no longer available in newZones anymore.
302 */
303 foreach($oldZones as $zone) {
304 $oldDn = "zoneName=".$zone['InitialzoneName'].",".$HostDn;
305 $del[$oldDn] = $zone;
306 }
308 /* Check for entries which must be updated
309 */
310 $zoneUpdates = array();
311 $udpate = array();
312 if(count($PrePareZoneEntries)){
313 $ldap = $config->get_ldap_link();
314 foreach($PrePareZoneEntries as $FromZoneName => $ToZoneName){
315 $ldap->cd($HostDn);
316 $ldap->search("(&(objectClass=dNSZone)(zoneName=".$FromZoneName.")(!(relativeDomainName=@)))",array("zoneName"));
317 while($attrs = $ldap->fetch()){
318 $zoneUpdates[$attrs['dn']] = array("zoneName"=>$ToZoneName);
319 }
320 }
321 }
323 $ret = array("del" => $del , "move" => $move , "add" => $add,"zoneUpdates"=>$zoneUpdates);
324 return($ret);
325 }
328 /* This static function returns the dns-host eintries for given
329 * name.
330 */
331 static function getDNSHostEntries($config,$name,$silent = false)
332 {
333 $types = array();
334 $ret = array();
335 $ret['RECORDS'] = array();
336 $ret['dNSClass'] = "IN";
337 $ret['zoneName'] = "";
338 $ret['dNSTTL'] = "7440";
339 $ret['exists'] = false;
341 $ldap = $config->get_ldap_link();
342 $ldap->cd($config->current['BASE']);
344 /* First check all zones for an entry with the given name.
345 * If the name occurs in more than one entry alert the user ...
346 */
347 $foundIn = array();
348 $zones = DNS::getAvailableZones($config);
350 $zonesArr = array();
351 foreach($zones as $zoneMix){
352 $zoneIndex = split("/",$zoneMix);
353 if(!array_key_exists($zoneIndex[0],$zonesArr)) {
354 $zonesArr[$zoneIndex[0]] = array();
355 }
356 array_push($zonesArr[$zoneIndex[0]],$zoneIndex[1]);
357 }
359 foreach($zonesArr as $nameServer => $nameServerArr){
360 $foundInTmp = array();
361 foreach($nameServerArr as $zoneArr => $zone){
362 $zoneMix = $nameServer."/".$zone;
363 $zoneDn = DNS::getDNSZoneDN($config,$zoneMix);
364 $ldap->ls("(&(objectClass=dNSZone)(zoneName=*)(relativeDomainName=".$name.")(!(relativeDomainName=@)))", $zoneDn,$attrs = array("*"));
365 while($attrs = $ldap->fetch()){
366 $foundInTmp [$zoneMix] = $attrs['dn'];
367 $foundIn [$zoneMix] = $attrs['dn'];
368 }
369 }
370 }
372 /* No zone found which contains an entry for us
373 */
374 if(count($foundIn) == 0){
375 return($ret);
376 }
378 /* Get host informations from zone
379 */
380 $id_tmp = key($foundIn);
381 $ldap->cd($foundIn[$id_tmp]);
382 $ldap->search("(&(objectClass=dNSZone)(zoneName=*)(!(relativeDomainName=@)))",array("*"));
383 while($attrs = $ldap->fetch()){
385 /* If relative domainname == cn
386 * Try to read dnsclass / TTl / zone
387 */
388 if($attrs['relativeDomainName'][0] == $name){
389 $ret['exists'] = true;
390 $ret['zoneName'] = $id_tmp;
391 foreach(array("dNSClass","dNSTTL") as $atr){
392 if(isset($attrs[$atr][0])){
393 $ret[$atr] = $attrs[$atr][0];
394 }
395 }
396 }
398 /* Create list with all used records */
399 foreach(DNS::$RecordTypes as $name => $value){
401 /* If there is a record attribute */
402 if(isset($attrs[$name])){
404 /* get all entries */
405 for($i = 0 ; $i < $attrs[$value]['count']; $i ++){
406 $types[] = array( "type" => $name,
407 "value" => $attrs[$value][$i]);
408 }
409 }
410 }
411 $ret['RECORDS'] = $types;
412 }
413 return($ret);
414 }
418 /* This static function compares two dns settings and returns an
419 * array with following indexes
420 * - delete, for dns which must be deleted (only if dns account is removed)
421 * - rename, if a dn must be renamed, for example, the relativeDomainName has changed
422 * - add, if there is a new dns account created
423 */
424 static function getDNSHostEntriesDiff($config,$oldName,$newEntry,$newName)
425 {
426 $oldEntry = DNS::getDNSHostEntries($config,$oldName);
428 $add = array();
429 $del = array();
430 $move = array();
432 /* Don't go further if there is nothing to do
433 * Is no account / was no account
434 */
435 if(($newEntry['exists'] == false )&& ($oldEntry['exists'] == false)){
436 return(array("move"=>$move,"add"=>$add,"del"=>$del));
437 }
439 $zones = DNS::getAvailableZones($config);
440 $specialAttributes = array("cNAMERecord","pTRRecord");
441 $newRecords = array(); // Used to remember which records are removed
442 $zoneNameMix = $newEntry['zoneName'];
443 $zoneDn = DNS::getDNSZoneDN($config,$zoneNameMix);
444 $tmp = array_flip($zones);
445 $zoneName = DNS::getNameFromMix($zoneNameMix);
447 /* If reverseZone can't be resolved ... this
448 * can't be a valid entry, so remove this account
449 */
450 if(isset($tmp[$zoneNameMix])){
451 $reverseNameMix = $tmp[$zoneNameMix];
452 $reverseDn = DNS::getDNSZoneDN($config,$reverseNameMix);
453 if(empty($reverseDn)){
454 $newEntry['exists'] = false;
455 }
456 }else{
457 $newEntry['exists'] = false;
458 }
460 /* If account was edited prepare some
461 * attributes & arrays ... if required add some
462 * dns to $move
463 */
464 if($oldEntry['exists']){
466 /* Check if the account was removed
467 */
468 if($newEntry['exists'] == false){
469 $dn = "relativeDomainName=".$oldName.",".DNS::getDNSZoneDN($config,$oldEntry['zoneName']);
470 $del[$dn] ="";
471 return(array("move"=>$move,"add"=>$add,"del"=>$del));
472 }
474 /* Check if zoneName has changed
475 */
476 if(count($newEntry['RECORDS'])){
477 if($oldEntry['zoneName'] != $newEntry['zoneName']){
478 $oldzoneDn = DNS::getDNSZoneDN($config,$oldEntry['zoneName']);
479 $dn = "relativeDomainName=".$oldName.",".$oldzoneDn;
480 $dn2= "relativeDomainName=".$oldName.",".$zoneDn;
481 $move[$dn]=$dn2;
482 }
484 /* Check if host name has changed
485 */
486 if($oldName != $newName){
487 $dn = "relativeDomainName=".$oldName.",".$zoneDn;
488 $dn2= "relativeDomainName=".$newName.",".$zoneDn;
489 $move[$dn]=$dn2;
490 $dn = "relativeDomainName=".$oldName.",".$dn2;
491 $dn2= "relativeDomainName=".$newName.",".$dn2;
492 $move[$dn]=$dn2;
493 }
494 }
496 /* Prepare record entries
497 * Fill old records with array();
498 * To ensure that they will be deleted if they stay unused
499 */
500 foreach($oldEntry['RECORDS'] as $id => $rec){
501 $newRecords[$rec['type']] = array();
502 }
503 }
505 /* There must be at least one record in our entry
506 */
507 if((!count($newEntry['RECORDS'])) || (!$newEntry['exists'])){
508 $dn = "relativeDomainName=".$newName.",".DNS::getDNSZoneDN($config,$oldEntry['zoneName']);
509 $del[$dn] ="";
510 $ret = array("move"=>$move,"add"=>$add,"del"=>$del);
511 return($ret);
512 }
514 /* Prepare temp obj
515 */
516 $baseObj = array();
517 $baseObj['objectClass'] = array("top","dNSZone");
518 $baseObj['dNSTTL'] = $newEntry['dNSTTL'];
519 $baseObj['dNSClass'] = $newEntry['dNSClass'];
520 $baseObj['zoneName'] = $zoneName;
521 $baseObj['relativeDomainName']= $newName;
523 /* Add Container Object to zone
524 * (this possibly already exists, check this before writing to ldap)
525 */
526 $baseDn = "relativeDomainName=".$newName.",".$zoneDn;
527 $add[$baseDn] = $baseObj;
529 /* Add base obejct which contains all std records
530 */
531 $stdDn = "relativeDomainName=".$newName.",".$baseDn;
532 $add[$stdDn] = $baseObj;
534 /* Set defaults. Normaly only contains old record names.
535 * The old names will be set to array, to ensure that they will be deleted.
536 * Or overwritten and filled with new values.
537 */
538 foreach($newRecords as $name => $def){
539 if(!in_array($name,$specialAttributes)){
540 $add[$stdDn][$name] = $def;
541 }
542 }
544 /* Delete all OLD special attributes.
545 */
546 foreach($oldEntry['RECORDS'] as $id => $rec){
547 if(in_array($rec['type'],$specialAttributes)){
548 $deldn= "relativeDomainName=".$rec['value'].",".$baseDn;
549 $del[$deldn] = "";
550 }
551 }
554 /* Create new record entries
555 */
556 foreach($newEntry['RECORDS'] as $id => $rec){
557 /* Create object which contains special records
558 * like pTRRecord or CNAMERecord
559 */
560 if($rec['type'] == "pTRRecord"){
561 $PTRdn= "relativeDomainName=".DNS::FlipIP($rec['value']).",".$baseDn;
562 $ptrObj = $baseObj;
563 $reverseName = DNS::getNameFromMix($reverseNameMix);
564 $ptrObj['zoneName'] = $reverseName;
565 if(!preg_match("/\.$/",$newName)){
566 $ptrObj['pTRRecord'] = preg_replace("/\.\.$/",".",$newName.".".$zoneName.".");
567 }else{
568 $ptrObj['pTRRecord'] = preg_replace("/\.\.$/",".",$newName.".");
569 }
570 $ptrObj['relativeDomainName'] = DNS::FlipIP($rec['value']);
572 $add[$PTRdn] = $ptrObj;
573 }else
574 if($rec['type'] == "cNAMERecord"){
575 $PTRdn= "relativeDomainName=".$rec['value'].",".$baseDn;
576 $ptrObj = $baseObj;
577 $ptrObj['zoneName'] = $zoneName;
578 $ptrObj['cNAMERecord'] = $newName;
579 $ptrObj['relativeDomainName'] = $rec['value'];
581 $add[$PTRdn] = $ptrObj;
582 }else{
583 /* Append basic attributes
584 */
585 $add[$stdDn][$rec['type']][] = $rec['value'];
586 }
587 } // foreach record
589 $ret = array("move"=>$move,"add"=>$add,"del"=>$del);
590 return($ret);
591 }
593 static function getNameFromMix($zoneMix){
594 $ret = "";
595 if(!strstr($zoneMix, '/')) return($ret);
596 $zoneIndex = split("/",$zoneMix);
597 return($zoneIndex[1]);
598 }
600 /* returns the dn for a specified zone
601 */
602 static function getDNSZoneDN($config,$zoneNameMix)
603 {
604 $ret = "";
605 if(!strstr($zoneNameMix, '/')) {
606 msg_dialog::display(_("Error"), sprintf(_("Undefined zone name '%s'!"),$zoneNameMix), ERROR_DIALOG);
607 return($ret);
608 }
610 $zoneNameIndex = split("/",$zoneNameMix);
611 $zoneName = $zoneNameIndex[1];
612 $nameServer = strtolower($zoneNameIndex[0]);
613 $ldap = $config->get_ldap_link();
615 /* search for the nameserver */
616 $ldap-> cd($config->current['BASE']);
617 $ldap->search("(&(objectClass=goServer)(cn=".$nameServer."))",array("cn"));
618 if($ldap->count()){
619 $attr = $ldap->fetch();
620 } else {
621 return($ret);
622 }
624 $ldap-> cd($attr['dn']);
625 $ldap->search("(&(objectClass=dNSZone)(sOARecord=*)(zoneName=".$zoneName."))",array("zoneName"));
626 if($ldap->count()){
627 $attr = $ldap->fetch();
628 return($attr['dn']);
629 }
631 return($ret);
632 }
635 /* returns all available zones
636 * array[reverseName] = zoneName;
637 */
638 static function getAvailableZones($config)
639 {
640 $ret = array();
641 $ldap = $config->get_ldap_link();
642 $ldap->cd ($config->current['BASE']);
644 /* Search for zones ...
645 */
646 $ldap->search("(&(objectClass=dNSZone)(sOARecord=*))",array("zoneName"));
648 $ForwardZones = array();
649 $ReverseZones = array();
650 $zones = array();
652 while($at = $ldap->fetch()){
653 if(preg_match("/\.in\-addr\.arpa/",$at['zoneName'][0])){
654 $ReverseZones[$at['dn']] = $at;
655 }else{
656 $ForwardZones[$at['dn']] = $at;
657 }
658 }
660 foreach($ForwardZones as $dn => $obj){
662 /* try to find reverse
663 */
664 foreach($ReverseZones as $Rdn => $Robj ){
665 if(preg_match("/".$dn."/",$Rdn)){
666 $zones[strtoupper($ldap->getCn($dn))."/".$Robj['zoneName'][0]] =
667 strtoupper($ldap->getCn($dn))."/".$obj['zoneName'][0];
668 }
669 }
670 }
671 return($zones);
672 }
673 }
674 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
675 ?>