#!/usr/bin/python

import string
import sys
import re

from Conf import *
import Zone
from Zone import TestError
# ConfNamed(Conf)
#  This class presents a data-oriented class for making changes
#  to the /var/named/named.conf file.
TRUE=1
FALSE=not TRUE
class namedObj:
    def __init__(self):
        self.comments=[]
        self.opts={}
    def setOpt(self,name,val):
        self.opts[name]=val
    def getOpt(self,name):
        return self.opts[name]
    def addComment(self,comment):
        self.comments.append(comment)
    def done(self):
        pass
    
class Servers(namedObj):
    def __init__(self,val):
        namedObj.__init__(self)
        self.type=val[0]
        if val[-1]=="};":
            for i in val[2:-1]:
                self.setOpt(i,"")
        
    def out(self):
        rec="\t%s { \n" % self.type
        for name in self.opts.keys():
            rec="%s\t\t%s %s\n" % (rec, name,self.getOpt(name))
        rec=rec+"\t};\n"
        return rec

class Comment(namedObj):
    def __init__(self,conf,line):
        namedObj.__init__(self)
        self.comments=[]
        self.comments.append(line)
        val=line.split()
        if val[0]=="/*":
            while conf.findnextline():
                val=conf.getline().split()
                line=conf.getline()
                conf.nextline()
                self.comments.append(line)
                if val=="":
                    continue
                if val[0]=="*/":
                    break
            
    def out(self):
        rec=""
        for comment in self.comments:
            rec="%s%s\n" % (rec, comment)
        return rec
        
class Options(namedObj):
    def __init__(self):
        namedObj.__init__(self)
    def setServers(self,f):
        self.servers=f
    def getServers(self):
        return self.servers
    def out(self):
        rec="options { \n"
        for comment in self.comments:
            rec="%s%s" % (rec,comment.out())
            
        for name in self.opts.keys():
            rec="%s\t%s %s\n" % (rec, name,self.getOpt(name))
        try:
            rec=rec+self.servers.out()
        except AttributeError,e:
            pass
        rec=rec+"};"
        return rec
        
class Logging(namedObj):
    def __init__(self):
        namedObj.__init__(self)
    def setServers(self,f):
        self.servers=f
    def getServers(self):
        return self.servers
    def out(self):
        rec="logging { \n"
        for comment in self.comments:
            rec="%s%s" % (rec,comment.out())
            
        for name in self.opts.keys():
            rec="%s\t%s %s\n" % (rec, name,self.getOpt(name))
        try:
            rec=rec+self.servers.out()
        except AttributeError,e:
            pass
        rec=rec+"};"
        return rec
        
class Controls(namedObj):
    def __init__(self):
        namedObj.__init__(self)

    def out(self):
        rec="controls { \n"
        for comment in self.comments:
            rec="%s%s" % (rec,comment.out())
        for name in self.opts.keys():
            rec="%s\t%s %s\n" % (rec, name,self.getOpt(name))
        rec=rec+"};"
        return rec
        
class ZoneFile(namedObj):
    def __init__(self,name,dir,parent):
        namedObj.__init__(self)
        self.name=name
        self.dir=dir.strip('"')
        self.zone=None
        self.parent=parent
        self.servers=None
        self.zone=None
        self.dirty=FALSE
        
    def done(self):
        self.zone=Zone.Zone(self.name,self.getFullFile())

    def getType(self):
        return self.getOpt("type")
    
    def save(self,uid,gid):
        if self.zone != None:
            self.zone.save(uid,gid)
        
    def test(self):
        if self.isSlave():
            return
        nslist=self.getNSList()
        if len(nslist) < 1:
            raise TestError, _("Zone `%s'\nmust list at least one nameserver record.\nUse the \"Add Record\" button to add a nameserver.") % self.getName()
                
    def getSOA(self):
        return self.zone.getSOA()

    def getNSList (self):
        return self.zone.getNSList()
    
    def getMXList (self):
        return self.zone.getMXList()
    
    def incSerial(self):
        self.zone.incSerial()

    def getAList (self):
        return self.zone.getAList()
    
    def getPTRList (self):
        return self.zone.getPTRList()
    
    def getCNAMEList (self):
        return self.zone.getCNAMEList()
    
    def setSerial(self,serial):
        self.zone.setSerial(serial);

    def getSerial(self):
        return self.zone.getSerial();

    def getName(self):
        return self.name

    def setName(self,name):
        self.name=name

    def getFile(self):
        return self.getOpt("file").strip('";')

    def setFile(self,file):
        return self.setOpt("file",'"%s";' % file)

    def getFullFile(self):
        return "%s/%s" % (self.parent.rootdir+self.dir.strip('";"'),self.getFile())

    def isSlave(self):
        type=self.getOpt("type").strip(";")        
        if type=="slave":
            return TRUE
        else:
            return FALSE

    def isMaster(self):
        type = self.getOpt("type").strip(";")        
        if type=="master":
            return TRUE
        else:
            return FALSE

    def isHint(self):
        type = self.getOpt("type").strip(";")        
        if type=="hint":
            return TRUE
        else:
            return FALSE
    
    def isReverse(self):
        x=self.name.split(".in-addr.arpa")
        return self.name!=x[0]

    def setServers(self,f):
        self.servers=f

    def getServers(self):
        return self.servers

    def getMasters(self):
        if self.servers != None:
            return self.servers.opts.keys()
        else:
            return []

    def testName(self,name):
        if self.isReverse():
            if not Zone.checkRevIpNum(name):
                raise TestError, _("Reverse zone name '%s' is not the end of a reverse IP address.") % name
            
        elif self.isSlave():
            if not Zone.hname_re.match(name):
                raise TestError, _("Slave zone name %s is not a valid domain name.") % name
        else:
            if not Zone.hname_re.match(name):
                raise TestError, _("Forward zone name %s is not a valid domain name.") % name

    def testFile(self,file):
        pass

    def unlink(self):
        self.parent.unlinkZone(self)
        
    def out(self):
        rec="zone \"%s\" { \n" % self.name
        for comment in self.comments:
            rec="%s%s" % (rec,comment.out())

        for name in self.opts.keys():
            rec="%s\t%s %s\n" % (rec, name,self.getOpt(name))
        try:
            rec=rec+self.servers.out()
        except AttributeError:
            pass
        rec=rec+"};"
        return rec
        
class ConfNamed(Conf):
    def __init__(self,filename,rootdir=""):
	Conf.__init__(self, rootdir+filename, commenttype='//')
        self.read()
        self.rewind()
        self.comments=[]
        self.zones=[]
        current=[]
        self.includes=[]
        self.rootdir=rootdir
        self.controls=None
        self.logging=None
        self.options=None
        while self.findnextline():
            val=self.getline().split()
            line=self.getline()
            self.nextline()
            if len(val)==0:
                continue
            if val[0]=="/*" or  val[0]=="//" or val[0][0]=="#":
                if (len(current)>0):
                    current[-1].addComment(Comment(self,line))
                else:
                    self.comments.append(Comment(self,line))
                continue
            if val[0]=="options":
                self.options=Options()
                current.append(self.options)
                continue
            if val[0]=="logging":
                self.logging=Logging()
                current.append(self.logging)
                continue
            if val[0]=="controls":
                self.controls=Controls()
                current.append(self.controls)
                continue
            if val[0]=="include":
                self.includes.append(val[1])
                continue
            if val[0] in ["forwarders","masters","allow-transfer","also-notify"]:
                f=Servers(val)
                current[-1].setServers(f)
                if val[-1]!="};":
                    current.append(f)
                continue
            if val[0]=="zone":
                zone=ZoneFile(val[1].strip('"'),self.getDirectory(),self)
                self.zones.append(zone)
                current.append(zone)
                continue
            if val[0]=="};":
                current[-1].done()
                current=current[:-1]
                continue
            current[-1].setOpt(val[0],join(val[1:],' '))

    def getDirectory(self):
        try:
            return self.options.getOpt("directory")
        except:
            return "/var/named"

    def getZone(self,zoneName):
        for z in self.zones:
            if z.getName()==zoneName:
                return z

    def addZone(self,name,file,type="master"):
        zone=ZoneFile(name,self.getDirectory(),self)
        zone.setOpt("file",'"%s";' % file)
        zone.setOpt("type",type+";")
        if type != "slave":
            zone.done()
        self.zones.append(zone)
        return zone
    
    def unlinkZone(self,zone):
        self.zones.remove(zone)
        
    def getForwardZoneList(self):
        l=[]
        for z in self.zones:
            if not z.isReverse() and z.isMaster():
                l.append(z)
        return l

    def getReverseZoneList(self):
        l=[]
        for z in self.zones:
            if z.isReverse() and z.isMaster():
                l.append(z)
        return l

    def getSlaveZoneList(self):
        l=[]
        for z in self.zones:
            if z.isSlave():
                l.append(z)
        return l

    def findnextcommentline(self):
        # optional whitespace followed by non-comment character
        # defines a codeline.  blank lines, lines with only whitespace,
        # and comment lines do not count.
        return self.findnextline('^[' + self.commenttype + ']+')
    
    def out(self):
        ret=""
        for comment in self.comments:
            ret="%s%s" % (ret,comment.out())
        if self.options!=None:
            ret="%s%s\n" % (ret,self.options.out())
        if self.controls!=None:
            ret="%s%s\n" % (ret,self.controls.out())
        if self.logging!=None:
            ret="%s%s\n" % (ret,self.logging.out())
        for z in self.zones:
            ret="%s%s\n" % (ret,z.out())
        for i in self.includes:
            ret="%sinclude\t %s\n" % (ret,i)
        return ret
    
    def getNamedUid(self):
        passwd = open('/etc/passwd')
        for line in passwd.readlines():
            rec = string.splitfields(line, ':')
            if rec[0] == "named":
                passwd.close()
                return string.atoi(rec[2])
        passwd.close()
        return 25

    def getNamedGid(self):
        group = open('/etc/group')
        for line in group.readlines():
            rec = string.splitfields(line, ':')
            if rec[0] == "named":
                group.close()
                return string.atoi(rec[2])
        group.close()
        return 25

    def save(self):
        tmpFile="%s.%d" % (self.filename,os.getpid())
        for z in self.zones:
            z.test()
        uid=self.getNamedUid()
        gid=self.getNamedGid()
        for z in self.zones:
            if not z.isHint():
                z.save(uid,gid)

        fd = open(tmpFile,"w")
        fd.write(self.out())
        fd.close()
        os.rename(tmpFile,self.filename)
        os.chown(self.filename,uid,gid)

    def saveLocal(self):
        self.save()
        
if __name__ == "__main__":
    c = ConfNamed("/etc/named.conf")
    print c.out()
