#!/usr/bin/python # Copyright (c) 2009 Andy Barclay (Unixpeople) # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Sun Jan 17 15:00:52 UTC 2016 import cgi, os, re, sys import cgitb cgitb.enable() import StringIO import dns.resolver import dns.zone import dns.query import dns.update import dns.tsigkeyring from types import ListType def htmlHeader(title): print "Content-type: text/html\n" print "

" + title + "

" # end htmlHeader def htmlFooter(): print '

Return to Main Page' print "" # end htmlFooter def gettype(id): m = re.match('^(.*):(.*):(.*)',id) return(m.group(1)) # end gettype def getdname(id): m = re.match('^(.*):(.*):(.*)',id) return(m.group(2)) # end getdname def getrdata(id): m = re.match('^(.*):(.*):(.*)',id) return(m.group(3)) # end getrdata def getTsigKey(zoneName): # zones in the file do not have "." at the end, so strip # the dot zoneName=zoneName.rstrip(".") # read all the zones from the file f=open(zonefile,'r') tsigkeyname=None for line in f: zlist=line.split() if (zlist[0]==zoneName): tsigkeyname=zlist[1] tsigkey=zlist[2] f.close() if (tsigkeyname == None): return(None) else: return(dns.tsigkeyring.from_text({tsigkeyname : tsigkey})) def getNameserver(zoneName): # zones in the file do not have "." at the end, so strip # the dot zoneName=zoneName.rstrip(".") # read all the zones from the file f=open(zonefile,'r') nameserver=None for line in f: zlist=line.split() if (zlist[0]==zoneName): # nameserver will always be the last in line nameserver=zlist[len(zlist)-1] f.close() if (nameserver == None): return(None) else: return(nameserver) def reverseIP(ip): m = re.match('(.*)\.(.*)\.(.*)\.(.*)',ip) return(m.group(4) + "." + m.group(3) + "." + m.group(2) + "." + m.group(1)) # end reverseip # returns the rest of the domain name passed - everything after first . def rest(ip): m = re.match('([^\.]*)\.(.*)',ip) return(m.group(2)) # end rest ################ main starts here ################## zonefile="/home/abarclay/config/zones.txt" #nameserver='192.168.37.12' #nameserver='107.21.97.57' try: TEST=os.environ["TEST"] except: TEST="false" if (TEST == "true"): # test basic functions print gettype("PTR:22:barclay.unixpeople.internal") print getdname("PTR:22:barclay.unixpeople.internal") print getrdata("PTR:22:barclay.unixpeople.internal") # test update # prepare the dns update myring = getTsigKey("unixpeople.com") nameserver = getNameserver("unixpeople.com") update = dns.update.Update("unixpeople.com", keyring=myring) # this succeeds, but doesn't work #update.delete("159.37.168.192.in-addr.arpa", "PTR", "alex.unixpeople.internal") # this succeeds, but doesn't work #update.delete("159.37.168.192.in-addr.arpa", "PTR", "alex.unixpeople.internal.") # this succeeds, but doesn't work #update.delete("159.37.168.192.in-addr.arpa.", "PTR", "alex.unixpeople.internal") # this one WORKS! # seems like both the dname and the rdata have to be fqdn #update.delete("162.37.168.192.in-addr.arpa.", "PTR", "kristen.unixpeople.internal.") # when we do two of them, it works too... #update.delete("172.37.168.192.in-addr.arpa.", "PTR", "DIRECTV-HR21-C30954D1.unixpeople.internal.") #update.delete("176.37.168.192.in-addr.arpa.", "PTR", "cessna.unixpeople.internal.") update.add("timeclock.unixpeople.com.", 86400, "CNAME", "annie.unixpeople.com.") #update.replace(rrname, myttl, mytype, myrrdata) response = dns.query.tcp(update, nameserver , timeout=10000) if (response.rcode()==0): print '

Success!

' else: print '

Failed!

' print '

rcode is

' print response.rcode() print response.to_text(response.rcode()) sys.exit(1) formVariables = cgi.FieldStorage() cgiScript="/cgi-bin/protected/dnstool.py" rrtypes=['ANY','A','CNAME','MX','NS','PTR','TXT', 'SOA'] # in future, lets add the name server to the zones.txt file try: myAction = formVariables["action"].value except: myAction = None if (myAction == 'Add a Resource Record'): htmlHeader("Add a Resource Record") # get zone to operate on try: myzone=formVariables["zone"].value except: print "You didn't select a zone to which you want to Add
" print "Go back and select a zone" htmlFooter() sys.exit(0) # get the rrtype to add try: mytype=formVariables["rrtype"].value formtype=dns.rdatatype.from_text(mytype) #print mytype #print formtype #print dns.rdatatype.A if (formtype==dns.rdatatype.ANY): raise Exception('spam','IncorrectType') except: print "You didn't select an rrtype to Add or you selected 'ANY'
" print "Go back and select a valid rrtype" htmlFooter() sys.exit(0) # form print '
' print '' # display the appropriate form for the rrtype if (formtype == dns.rdatatype.SOA): print "Not Implemented" #print formtype == dns.rdatatype.A if (formtype == dns.rdatatype.A): print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypeIP Address
' + "." + myzone print '' print 'IN' print 'A' print '
' print '
Automatically add PTR record

' if (formtype == dns.rdatatype.CNAME): print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypeCNAME
' + "." + myzone print '' print 'IN' print 'CNAME' print '
' if (formtype == dns.rdatatype.MX): print '' print '' print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypePreferenceMail Server
' + "." + myzone print '' print 'IN' print 'MX' print '
' if (formtype == dns.rdatatype.NS): print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypeName Server
' + "." + myzone print '' print 'IN' print 'NS' print '
' print "Not Implemented" if (formtype == dns.rdatatype.PTR): print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypeCanonical Name
' + "." + myzone print '' print 'IN' print 'PTR' print '
' print '

NOTE: PTR records added by this tool will have a "." appended if they are not fully qualified

' if (formtype == dns.rdatatype.TXT): print '' print '' print '' print '' print '' print '' print '' print '
Domain NameTTLClassTypeCharacter Strings
' + "." + myzone print '' print 'IN' print 'TXT' print '
' print '' print '' print '

' htmlFooter() elif (myAction == 'View Zone'): try: myzone=formVariables["zone"].value nameserver = getNameserver(myzone) formtype=dns.rdatatype.from_text(formVariables["rrtype"].value) except: print "You didn't select a zone and rrtype to view
" print "Go back and select a zone first" htmlFooter() sys.exit(0) htmlHeader(myAction + ": " + myzone + " from server: " + nameserver) try: z=dns.zone.from_xfr(dns.query.xfr(nameserver,myzone)) except: print myzone + " doesn't seem to exist at nameserver: " + nameserver + "
" print "Go back and select a zone that exists" htmlFooter() sys.exit(0) names = z.nodes.keys() # execute the dns query, parse the output and display the table print '
' print '' print '' print '' for n in names: rrset=z[n] for rr in rrset: #print n.to_text(n) #print rr #print rr.rdtype #print '

' # if typename is not ANY, only show records of specified type if (formtype != dns.rdatatype.ANY): if (rr.rdtype != formtype): continue # SOA if (rr.rdtype == dns.rdatatype.SOA): mydata=str(rr[0].mname) + " " + str(rr[0].rname) + " " + str(rr[0].serial) + " " + str(rr[0].refresh) + " " + str(rr[0].retry) + " " + str(rr[0].expire) + " " + str(rr[0].minimum) # TXT elif (rr.rdtype == dns.rdatatype.TXT): mydata=rr[0].strings # MX elif (rr.rdtype == dns.rdatatype.MX): mydata=str(rr[0].preference) + " " + rr[0].exchange.to_text(rr[0].exchange) # CNAME elif (rr.rdtype == dns.rdatatype.CNAME): mydata=rr[0].target.to_text() # A elif (rr.rdtype == dns.rdatatype.A): mydata=rr[0].address # PTR elif (rr.rdtype == dns.rdatatype.PTR): mydata=rr[0].target else: continue id=dns.rdatatype.to_text(rr.rdtype) + ":" + n.to_text(n) # check to see if mydata is a list if isinstance(mydata, ListType): for myid in mydata: id=id + ":" + myid elif isinstance(mydata, dns.name.Name): if mydata.is_absolute(): id=id + ":" + mydata.to_text(mydata) else: id=id + ":" + mydata.to_text(mydata) + "." + myzone + "." else: id=id + ":" + mydata print '

' print '' print '' print '' print '' print '' print '' print '
Select Domain Name TTL Type Data
' + n.to_text(n) + '' print rr.ttl print '' myrrtype=dns.rdatatype.to_text(rr.rdtype) print myrrtype print '' print mydata print '
' print '' print '' print '' print '' print '
' htmlFooter() elif (myAction == None) or (myAction == "Choose a different Zone"): htmlHeader("DNS Maintenance Tool") # count the lines in the zones file f=open(zonefile,'r') numzones=0 zonelist=[] for line in f: numzones=numzones+1 z=line.split() zonelist.append(z[0]) f.close() zonelist=sorted(zonelist) # enforce some reasonable values if (numzones < 10): numzones=10 if (numzones > 20): numzones=20 print '
' print 'Choose a zone to operate on:
' print '(Optionally limit the Records to the chosen type)

' print '' # print out the rr types print '

' print '' print '' print '' print '

' print '

' htmlFooter() elif (myAction == "Edit Selected Resource Record"): try: myzone=formVariables["zone"].value nameserver = getNameserver(myzone) formtype=dns.rdatatype.from_text(formVariables["rrtype"].value) except: print "You didn't select a zone and rrtype to view
" print "Go back and select a zone first" htmlFooter() sys.exit(0) htmlHeader(myAction + ": " + myzone + " from server: " + nameserver) print "not implemented" htmlFooter() elif (myAction == "Delete Selected Resource Records"): htmlHeader("DNS Maintenance Tool") try: myzone=formVariables["zone"].value nameserver = getNameserver(myzone) except: print "zones.txt file does not appear to contain a nameserver for zone: " + myzone + "
" htmlFooter() sys.exit(0) # prepare the dns update keyring = getTsigKey(myzone) update = dns.update.Update(myzone, keyring=keyring) # fully qualify the zonename myzone=myzone + "." # "selected" could be a list or a variable try: # assume it's a list... for id in formVariables["selected"]: print "about to delete " + id.value + " from " + myzone + '
' myrrtype=gettype(id.value) print "
myrrtype:" + myrrtype + "
" mydname=getdname(id.value) mydname=mydname + "." + myzone print "
mydame:" + mydname + "
" myrdata=getrdata(id.value) print "
myrdata:" + myrdata + "
" print "
calling delete with " + mydname + " " + myrrtype + " " + myrdata update.delete(mydname, myrrtype, myrdata) except KeyError: print "You didn't select any records to delete
" print "Go back and select one or more records first" htmlFooter() sys.exit(0) except: # if selected was not a list, then we'll end up here... id=formVariables["selected"] print "about to delete " + id.value + " from " + myzone myrrtype=gettype(id.value) print "
myrrtype:" + myrrtype + "
" mydname=getdname(id.value) mydname=mydname + "." + myzone print "
mydame:" + mydname + "
" myrdata=getrdata(id.value) print "
myrdata:" + myrdata + "
" print "
calling delete with " + mydname + " " + myrrtype + " " + myrdata update.delete(mydname, myrrtype, myrdata) response = dns.query.tcp(update, nameserver , timeout=10) if (response.rcode()==0): print '

Success!' else: print '

Failed!' htmlFooter() elif (myAction == 'Add Record'): htmlHeader("DNS Maintenance Tool") try: myzone=formVariables["zone"].value nameserver = getNameserver(myzone) except: print "zones.txt file does not appear to contain a nameserver for zone: " + myzone + "
" htmlFooter() sys.exit(0) # prepare the dns update keyring = getTsigKey(myzone) update = dns.update.Update(myzone, keyring=keyring) # the type will always be set mytype=formVariables["rrtype"].value myclass=formVariables["class"].value myttl=formVariables["ttl"].value # based on type, names of vars might be different if (mytype == 'MX'): try: myrrdata=str(formVariables["preference"].value) + " " + formVariables["mailserver"].value except: print 'looks like you did not specify a preference and mail server' print 'go back and specify them' htmlFooter() sys.exit(0) # otherwise, its a common name else: try: rrname=formVariables["rrname"].value except: rrname="" try: myrrdata=formVariables["rrdata"].value except: print "You didn't type in anything for the rdata
" print "Go back and type something in" htmlFooter() sys.exit(0) # see if it's fully qualified fq=re.compile(".*\.$") if ((mytype == 'PTR') and (not (fq.match(myrrdata)))): myrrdata = myrrdata + "." print "about to add " + rrname + " " + myttl + " " + myclass + " " + mytype + " " + myrrdata + " to " + myzone + '
' # changed from update.replace update.add(rrname, myttl, mytype, myrrdata) response = dns.query.tcp(update, nameserver , timeout=10) if (response.rcode()==0): print '

Success!' else: print '

Failed!' # if the record type is an A record and addptr is true, then add # the ptr record too try: addptr=formVariables["addptr"].value except: addptr="0" if ((mytype == 'A') and (addptr=='1')): print '

Adding PTR too!' tmpname=rrname rrname=reverseIP(myrrdata) + ".in-addr.arpa." originalzone=myzone myzone=rrname myrrdata=tmpname # if myrrdata not fully qualified, add original myzone fq=re.compile(".*\.$") if (not (fq.match(myrrdata))): myrrdata = myrrdata + "." + originalzone + "." mytype="PTR" keyring=None while ((keyring==None) and (myzone != "")): print "looking for zone " + myzone + '
' myzone=rest(myzone) keyring = getTsigKey(myzone) if (myzone != ""): update = dns.update.Update(myzone, keyring=keyring) update.replace(rrname, myttl, mytype, myrrdata) print "about to add " + rrname + " " + myttl + " " + myclass + " " + mytype + " " + myrrdata + " to " + myzone + '
' response = dns.query.tcp(update, nameserver , timeout=10) else: print "unable to add PTR - can't find zone" htmlFooter() else: htmlHeader("DNS Maintenance Tool") print "unknown action" htmlFooter()