From 2dabc3bdd2d59f3daa334002bc22c793c69223ca Mon Sep 17 00:00:00 2001
From: John Eckersberg <jeckersb@redhat.com>
Date: Thu, 8 May 2008 22:27:24 -0400
Subject: [PATCH] fix zone config matching and minimize the number of zone files generated

---
 cobbler/modules/manage_bind.py |  120 ++++++++++++++++++++++++----------------
 docs/cobbler.pod               |    8 +--
 2 files changed, 75 insertions(+), 53 deletions(-)

diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py
index e14122a..339fbf0 100644
--- a/cobbler/modules/manage_bind.py
+++ b/cobbler/modules/manage_bind.py
@@ -23,6 +23,7 @@ import glob
 import traceback
 import errno
 import popen2
+import re
 from shlex import shlex
 
 
@@ -69,9 +70,13 @@ class BindManager:
 
     def __forward_zones(self):
         """
-        Returns ALL forward zones for all systems
+        Returns a map of zones and the records that belong
+        in them
         """
         zones = {}
+        for zone in self.settings.manage_forward_zones:
+           zones[zone] = []
+
         for sys in self.systems:
             for (name, interface) in sys.interfaces.iteritems():
                 host = interface["hostname"]
@@ -81,19 +86,45 @@ class BindManager:
                     continue
                 if host.find(".") == -1:
                     continue
-                tokens = host.split('.')
-                zone = '.'.join(tokens[1:])
-                if zones.has_key(zone):
-                    zones[zone].append((host.split('.')[0], ip))
-                else:
-                    zones[zone] = [(host.split('.')[0], ip)]
+
+                # match the longest zone!
+                # e.g. if you have a host a.b.c.d.e
+                # if manage_forward_zones has:
+                # - c.d.e
+                # - b.c.d.e
+                # then a.b.c.d.e should go in b.c.d.e
+                best_match = ''
+                for zone in zones.keys():
+                   if re.search('\.%s$' % zone, host) and len(zone) > len(best_match):
+                      best_match = zone
+
+                if best_match == '': # no match
+                   continue
+
+                # strip the zone off the hostname and append the
+                # remainder + ip to the zone list
+                host = host.replace(best_match, '')
+                if host[-1] == '.': # strip trailing '.' if it's there
+                   host = host[:-1]
+                zones[best_match].append((host, ip))
+
+        # axe zones that are defined in manage_forward_zones
+        # but don't actually match any hosts
+        for (k,v) in zones.items():
+           if v == []:
+              zones.pop(k)
+
         return zones
 
     def __reverse_zones(self):
         """
-        Returns ALL reverse zones for all systems
+        Returns a map of zones and the records that belong
+        in them
         """
         zones = {}
+        for zone in self.settings.manage_reverse_zones:
+           zones[zone] = []
+
         for sys in self.systems:
             for (name, interface) in sys.interfaces.iteritems():
                 host = interface["hostname"]
@@ -101,45 +132,40 @@ class BindManager:
                 if not host or not ip:
                     # gotsta have some hostname and ip or else!
                     continue
+
+                # match the longest zone!
+                # e.g. if you have an ip 1.2.3.4
+                # if manage_reverse_zones has:
+                # - 1.2
+                # - 1.2.3
+                # then 1.2.3.4 should go in 1.2.3
+                best_match = ''
+                for zone in zones.keys():
+                   if re.search('^%s\.' % zone, ip) and len(zone) > len(best_match):
+                      best_match = zone
+
+                if best_match == '': # no match
+                   continue
+
+                # strip the zone off the front of the ip
+                # reverse the rest of the octets
+                # append the remainder + hostname
+                ip = ip.replace(best_match, '', 1)
+                if ip[0] == '.': # strip leading '.' if it's there
+                   ip = ip[1:]
                 tokens = ip.split('.')
-                zone = '.'.join(tokens[0:3])
-                if zones.has_key(zone):
-                    zones[zone].append((tokens[3], host + '.'))
-                else:
-                    zones[zone] = [(tokens[3], host + '.')]
-        return zones
+                tokens.reverse()
+                ip = '.'.join(tokens)
+                zones[best_match].append((ip, host + '.'))
 
-    def __config_forward_zones(self):
-        """
-        Returns only the forward zones which have systems and are defined
-        in the option manage_forward_zones
+        # axe zones that are defined in manage_forward_zones
+        # but don't actually match any hosts
+        for (k,v) in zones.items():
+           if v == []:
+              zones.pop(k)
 
-        Alternatively if manage_forward_zones is empty, return all systems
-        """
-        all = self.__forward_zones()
-        want = self.settings.manage_forward_zones
-        if want == []: return all
-        ret = {}
-        for zone in all.keys():
-            if zone in want:
-                ret[zone] = all[zone]
-        return ret
-
-    def __config_reverse_zones(self):
-        """
-        Returns only the reverse zones which have systems and are defined
-        in the option manage_reverse_zones
+        return zones
 
-        Alternatively if manage_reverse_zones is empty, return all systems
-        """
-        all = self.__reverse_zones()
-        want = self.settings.manage_reverse_zones
-        if want == []: return all
-        ret = {}
-        for zone in all.keys():
-            if zone in want:
-                ret[zone] = all[zone]
-        return ret
 
     def __write_named_conf(self):
         """
@@ -151,7 +177,7 @@ class BindManager:
         reverse_zones = self.settings.manage_reverse_zones
 
         metadata = {'zone_include': ''}
-        for zone in self.__config_forward_zones().keys():
+        for zone in self.__forward_zones().keys():
                 txt =  """
 zone "%(zone)s." {
     type master;
@@ -160,7 +186,7 @@ zone "%(zone)s." {
 """ % {'zone': zone}
                 metadata['zone_include'] = metadata['zone_include'] + txt
 
-        for zone in self.__config_reverse_zones().keys():
+        for zone in self.__reverse_zones().keys():
                 tokens = zone.split('.')
                 tokens.reverse()
                 arpa = '.'.join(tokens) + '.in-addr.arpa'
@@ -189,8 +215,8 @@ zone "%(arpa)s." {
         default_template_file = "/etc/cobbler/zone.template"
         cobbler_server = self.settings.server
         serial = int(time.time())
-        forward = self.__config_forward_zones()
-        reverse = self.__config_reverse_zones()
+        forward = self.__forward_zones()
+        reverse = self.__reverse_zones()
 
         try:
             f2 = open(default_template_file,"r")
diff --git a/docs/cobbler.pod b/docs/cobbler.pod
index 9f5154e..8126d57 100644
--- a/docs/cobbler.pod
+++ b/docs/cobbler.pod
@@ -565,7 +561,7 @@ Cobbler can optionally manage DNS configuration using BIND and dnsmasq.
 
 Choose either "management = isc_and_bind" or "management = dnsmasq" in /etc/cobbler/modules.conf and then enable manage_dns in /var/lib/cobbler/settings.
 
-This feature is off by default.  If using BIND, you may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'.  (See the Wiki for more information on this).
+This feature is off by default.  If using BIND, you must define the zones to be managed with the options 'manage_forward_zones' and 'manage_reverse_zones'.  (See the Wiki for more information on this).
 
 If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively.  You may drop zone-specific template files in /etc/cobbler/zone_templates/name-of-zone which will override the default.  These files must be user edited for the user's particular networking environment.  Read the file and understand how BIND works before proceeding.
 
-- 
1.5.4.1

