Add support for basic quoting in the DNS zone file
[invirt/packages/invirt-dns.git] / invirt-dns
index 13e00d4..4193be1 100755 (executable)
@@ -12,6 +12,7 @@ import invirt.database
 import psycopg2
 import sqlalchemy
 import time
+import re
 
 class DatabaseAuthority(common.ResolverBase):
     """An Authority that is loaded from a file."""
@@ -91,12 +92,6 @@ class DatabaseAuthority(common.ResolverBase):
                     results.append(dns.RRHeader(domain, dns.SOA, dns.IN,
                                                 ttl, self.soa, auth=True))
             else: # Request for a subdomain.
-                if 'passup' in dir(config.dns) and host in config.dns.passup:
-                    record = dns.Record_CNAME('%s.%s' % (host, config.dns.parent), ttl)
-                    return defer.succeed((
-                        [dns.RRHeader(name, dns.CNAME, dns.IN, ttl, record, auth=True)],
-                        [], []))
-
                 value = invirt.database.Machine.query().filter_by(name=host).first()
                 if value is None or not value.nics:
                     return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
@@ -119,11 +114,77 @@ class DatabaseAuthority(common.ResolverBase):
             #Doesn't exist
             return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
 
+class QuotingBindAuthority(authority.BindAuthority):
+    """
+    A BindAuthority that (almost) deals with quoting correctly
+    
+    This will catch double quotes as marking the start or end of a
+    quoted phrase, unless the double quote is escaped by a backslash
+    """
+    # Grab everything up to the first whitespace character or
+    # quotation mark not proceeded by a backslash
+    whitespace_re = re.compile(r'(.*?)([\t\n\x0b\x0c\r ]+|(?<!\\)")')
+    def collapseContinuations(self, lines):
+        L = []
+        state = 0
+        for line in lines:
+            if state == 0:
+                if line.find('(') == -1:
+                    L.append(line)
+                else:
+                    L.append(line[:line.find('(')])
+                    state = 1
+            else:
+                if line.find(')') != -1:
+                    L[-1] += ' ' + line[:line.find(')')]
+                    state = 0
+                else:
+                    L[-1] += ' ' + line
+        lines = L
+        L = []
+        for line in lines:
+            in_quote = False
+            split_line = []
+            while len(line) > 0:
+                match = self.whitespace_re.match(line)
+                if match is None:
+                    # If there's no match, that means that there's no
+                    # whitespace in the rest of the line, so it should
+                    # be treated as a single entity, quoted or not
+                    #
+                    # This also means that a closing quote isn't
+                    # strictly necessary if the line ends the quote
+                    substr = line
+                    end = ''
+                else:
+                    substr, end = match.groups()
+                
+                if in_quote:
+                    # If we're in the middle of the quote, the string
+                    # we just grabbed belongs at the end of the
+                    # previous string
+                    #
+                    # Including the whitespace! Unless it's not
+                    # whitespace and is actually a closequote instead
+                    split_line[-1] += substr + (end if end != '"' else '')
+                else:
+                    # If we're not in the middle of a quote, than this
+                    # is the next new string
+                    split_line.append(substr)
+                
+                if end == '"':
+                    in_quote = not in_quote
+                
+                # Then strip off what we just processed
+                line = line[len(substr + end):]
+            L.append(split_line)
+        return filter(None, L)
+
 if '__main__' == __name__:
     resolvers = []
     for zone in config.dns.zone_files:
         for origin in config.dns.domains:
-            r = authority.BindAuthority(zone)
+            r = QuotingBindAuthority(zone)
             # This sucks, but if I want a generic zone file, I have to
             # reload the information by hand
             r.origin = origin