#!/usr/bin/python
import sys
import os
import pprint
import copy

class ZoneRipper:
  def __init__(self, zonename):
    self.zonename = zonename
    self.cfgTokens = []
    self.cfgStr = ""
    self.curToken = 0
    self.saveToken = 0
    self.zone = {}
    return None

  def consume(self):
    _item = self.cfgTokens[self.curToken]
    self.curToken+=1
    return _item

  def peek(self):
    return self.cfgTokens[self.curToken]

  def tokensAvail(self):
    return self.curToken<len(self.cfgTokens)

  def tokenize(self):
    # Yes, this is an ugly hack of a tokenizer. Feel free to fix.
    self.cfgStr = self.cfgStr.replace('}', ' } ')
    self.cfgStr = self.cfgStr.replace(';', ' ; ')
    self.cfgStr = self.cfgStr.replace('"', ' " ')
    self.cfgTokens = self.cfgStr.split()

  def uncommentAndStore(self, string):
    inComment = 0
    for line in string.split('\n'):
      line = line.strip()
      if line.count(';')>0:
        self.cfgStr += line.split(';')[0].strip()+' '
        continue
      if line.count('#')>0:
        self.cfgStr += line.split('#')[0].strip()+' '
        continue
      if line.count('//')>0:
        self.cfgStr += line.split('//')[0].strip()+' '
        continue
      if line.count('/*')>0:
        inComment = 1
        self.cfgStr += line.split('/*')[0].strip()+' '
        continue
      if inComment and line.count('*/')==0:
        continue
      if inComment and line.count('*/')>0:
        inComment = 0
        self.cfgStr += line.split('*/')[1].strip()+' '
        continue
      self.cfgStr += line + ' '
    return

  def parseFile(self, filename):
    self.filename = filename
    if not os.path.exists(self.filename):
      return
    if not os.path.isfile(self.filename):
      return
    try:
      self.fp = open(self.filename)
    except:
      return
    self.uncommentAndStore(self.fp.read())
    self.tokenize()
    self.parse()

  def parseString(self, string):
    self.uncommentAndStore(string)
    self.tokenize()
    self.parse()

  def parse(self):
    while self.tokensAvail():
      token = self.consume()
      if token.startswith('$'):
        self.processDirective(token)
        continue
      if token == '@':
        itemName = self.zonename
        itemType = self.consume()
      elif token == 'IN':
        itemName = self.zonename
        itemType = token
      else:
        itemName = token
        itemType = self.consume()
      recordType = self.consume()
      if recordType == 'SOA':
        self.zone['soa'] = self.processSOA(itemName, itemType, recordType)
        continue
      elif recordType == 'A':
        if not self.zone.has_key('addresses'):
          self.zone['addresses'] = {}
        self.zone['addresses'][itemName] = self.consume()
        continue
      elif recordType == 'NS':
        if not self.zone.has_key('nameservers'):
          self.zone['nameservers'] = {}
        nstgt = self.consume()
        if not self.zone['nameservers'].has_key(itemName):
          self.zone['nameservers'][itemName] = []
        self.zone['nameservers'][itemName].append(nstgt)
        continue
      elif recordType == 'MX':
        if not self.zone.has_key('mailservers'):
          self.zone['mailservers'] = {}
        prio = self.consume()
        serv = self.consume()
        if not self.zone['mailservers'].has_key(itemName):
          self.zone['mailservers'][itemName] = []
        self.zone['mailservers'][itemName].append((serv, prio))
        continue
      elif recordType == 'CNAME':
        if not self.zone.has_key('aliases'):
          self.zone['aliases'] = {}
        self.zone['aliases'][itemName] = self.consume()
        continue
      elif recordType == 'SRV':
        prio = self.consume()
        weight = self.consume()
        port = self.consume()
        target = self.consume()
        if not self.zone.has_key('services'):
          self.zone['services'] = []
        self.zone['services'].append( (target, port, prio, weight) )
        continue
      elif recordType == 'PTR':
        if not self.zone.has_key('reverses'):
          self.zone['reverses'] = {}
        self.zone['reverses'][itemName] = self.consume()
        continue
      elif recordType == 'TXT':
        if not self.zone.has_key('txt'):
          self.zone['txt'] = {}
        if self.peek() == '"':
          self.zone['txt'][itemName] = self.readQuotedField()
        else:
          self.zone['txt'][itemName] = self.consume()
        continue
      else:
        print "Strange recordtype encountered: "+recordType
        print "Zone being parsed: "+self.zonename
        print "Next token: "+self.peek()
        return

  def readQuotedField(self):
    val = ""
    token = self.consume()
    if not token == '"':
      print "Parser fail - no open of quoted field for TXT record."
    token = self.consume()
    while token != '"':
      val = val+token
      if self.peek() != '"':
        val = val+' '
      token = self.consume()
    return val

  def processSOA(self, zone, entry, record):
    soaRecord = {}
    soaRecord['domain'] = zone
    soaRecord['masterServer'] = self.consume()
    soaRecord['mailbox'] = self.consume()
    token = self.consume()
    if token != '(':
      raise Exception("Parse error - SOA record messed up")
    soaRecord['serial'] = self.consume()
    soaRecord['refresh'] = self.consume()
    soaRecord['retry'] = self.consume()
    soaRecord['expire'] = self.consume()
    soaRecord['ttl'] = self.consume()
    token = self.consume()
    if token != ')':
      raise Exception("Parse error - SOA record exit missed")
    return soaRecord
      
  def processDirective(self, directive):
    if directive == '$TTL':
      value = self.consume()
      if not self.zone.has_key('globalvars'):
        self.zone['globalvars'] = {}
      self.zone['globalvars'][directive] = value
    elif directive == '$INCLUDE':
      subFile = self.consume()
      zoneInc = ZoneRipper(self.zonename)
      zoneInc.parseFile(subFile)
      subZone = zoneInc.getZone()
      for key in subZone.keys():
        if self.zone.has_key(key):
          for otherkey in zoneInc[key].keys():
            self.zone[key][otherkey] = copy.deepcopy(subZone[key][otherkey])
        else:
          self.zone[key] = copy.deepcopy(subZone[key])
    else:
      print "Directive "+directive+" not understood!"
      raise Exception("Parse fail - funky directive")
    return

  def printZone(self):
    pprint.pprint(self.zone)

  def getZone(self):
    return copy.deepcopy(self.zone)

def runTests():
  zr = ZoneRipper("railinc.com")
  zr.parseFile('/home/itjxb01/named-copy/int.railinc.com')
  zr.printZone()
  zr2 = ZoneRipper("11.10.10.in-addr.arpa")
  zr2.parseFile('/home/itjxb01/named-copy/db.10.10.11')
  zr2.printZone()
if __name__ == '__main__':
  runTests()
