more cleanup, adding docstrings, bit more exception handling.

This commit is contained in:
Jeffrey Forman 2012-11-18 21:10:09 -05:00
parent 94fa3cbb43
commit fc971391b9
4 changed files with 115 additions and 90 deletions

View File

@ -1,4 +1,6 @@
from binder import keyutils
from binder import keyutils, exceptions
# TODO: Start using exceptions here, force a record/add/delete on
# an unresponsive Bind server.
import re
import dns.query
@ -15,10 +17,9 @@ def add_forward_record(dns_server, zone_name, record_name, record_type, record_d
dns_update = dns.update.Update(zone_name, keyring = keyring)
dns_update.replace(record_name, ttl, record_type, record_data)
output = dns.query.tcp(dns_update, dns_server)
response = dns.query.tcp(dns_update, dns_server)
return response
return output
def add_reverse_record(dns_server, zone_name, record_name, record_data, ttl, keyring):
""" Given passed arguments, add/update a reverse PTR record."""
@ -66,10 +67,10 @@ def add_record(form_data):
def add_cname_record(dns_server, zone_name, originating_record, cname, ttl, key_name):
"""Add a Cname record."""
if key_name is None:
if key_name == "None":
keyring = None
else:
this_key = models.Key.objects.get(name=str(key_name))
this_key = models.Key.objects.get(name=key_name)
keyring = keyutils.create_keyring(this_key.name, this_key.data)
update = dns.update.Update(zone_name, keyring = keyring)
@ -98,6 +99,7 @@ def delete_record(form_data, rr_items):
dns_update = dns.update.Update(domain, keyring = keyring)
dns_update.delete(record)
output = dns.query.tcp(dns_update, dns_server)
delete_response.append({ "description" : "Delete record %s" % current_rr_item, "output" : output })
delete_response.append({ "description" : "Delete record %s" % current_rr_item,
"output" : output })
return delete_response

View File

@ -12,6 +12,6 @@ def create_keyring(key_name, key_data):
keyring = dns.tsigkeyring.from_text({
key_name : key_data
})
})
return keyring

View File

@ -1,27 +1,38 @@
from BeautifulSoup import BeautifulStoneSoup as BS
from django.db import models
from binder import exceptions
from django.db import models
import dns.query
import dns.zone
import dns.tsig
import dns.zone
import keyutils
import re
import urllib2
TSIG_ALGORITHMS = (('hmac-md5', 'MD5'),('hmac-sha1', 'SHA1'),('hmac-224', 'SHA224'),('hmac-sha256', 'SHA256'),('hmac-sha384', 'SHA384'),('hmac-sha512', 'SHA512'))
TSIG_ALGORITHMS = (('hmac-md5', 'MD5'),
('hmac-sha1', 'SHA1'),
('hmac-sha256', 'SHA256'),
('hmac-sha384', 'SHA384'),
('hmac-sha512', 'SHA512'))
class Key(models.Model):
name = models.CharField(max_length=50)
data = models.CharField(max_length=150)
algorithm = models.CharField(max_length=200, choices=TSIG_ALGORITHMS)
""" Class to store and reference TSIG keys.
TODO: Should/Can we encrypt these DNS keys in the DB?
"""
name = models.CharField(max_length=255)
data = models.CharField(max_length=255)
algorithm = models.CharField(max_length=255, choices=TSIG_ALGORITHMS)
def __unicode__(self):
return self.name
class BindServer(models.Model):
hostname = models.CharField(max_length=100)
""" Class to store DNS servers and attributes for referencing their
statistics ports. Also reference FK for TSIG transfer keys,
if required.
"""
hostname = models.CharField(max_length=255)
statistics_port = models.IntegerField()
default_transfer_key = models.ForeignKey(Key, null=True, blank=True)
@ -29,12 +40,16 @@ class BindServer(models.Model):
return self.hostname
def list_zones(self):
""" Take a DNS server, and list the DNS zones it provides resolution for. """
# I should take the dns_hostname here, get the object from the DB,
# and use the status port attribute for the urllib2 query.
myreq = urllib2.Request("http://%s:%s" % (self.hostname, self.statistics_port))
""" List the DNS zones and attributes.
TODO: Parse these XML more intelligently. Grab the view name. Any other data available?
Returns:
List of Dicts { String zone_name, String zone_serial }
"""
zone_req = urllib2.Request("http://%s:%s" % (self.hostname, self.statistics_port))
try:
http_request = urllib2.urlopen(myreq)
http_request = urllib2.urlopen(zone_req)
except urllib2.URLError, err:
raise exceptions.ZoneException(err)
@ -42,7 +57,7 @@ class BindServer(models.Model):
xmloutput = http_request.read()
mysoup = BS(xmloutput)
zones = mysoup.findAll('zone')
for current_zone in zones: # Interate over found zones
for current_zone in zones:
zone_name = current_zone.find("name").string.split("/IN")[0]
zone_serial = current_zone.find("serial").string
zone_class = current_zone.find("rdataclass").string
@ -51,9 +66,18 @@ class BindServer(models.Model):
return return_array
def list_zone_records(self, zone):
"""Given a zone, produce an array of dicts containing
each RR record and its attributes."""
def list_zone_records(self, zone_name):
""" List all records in a specific zone.
TODO: Print out current_record in the loop and see if we can parse this more programatically,
rather than just splitting on space. What is the difference between class and type?
Arguments:
String zone_name: Name of the zone
Returns:
List of Dicts { String rr_name, String rr_ttl, String rr_class, String rr_type, String rr_data }
"""
if self.default_transfer_key:
keyring = keyutils.create_keyring(self.default_transfer_key.name,
self.default_transfer_key.data)
@ -61,7 +85,7 @@ class BindServer(models.Model):
keyring = None
try:
zone = dns.zone.from_xfr(dns.query.xfr(self.hostname, zone, keyring=keyring))
zone = dns.zone.from_xfr(dns.query.xfr(self.hostname, zone_name, keyring=keyring))
except dns.exception.FormError, err:
raise exceptions.TransferException("There was an error attempting to list zone records.")
except dns.tsig.PeerBadKey:
@ -72,11 +96,16 @@ class BindServer(models.Model):
record_array = []
for current_name in names:
current_record = zone[current_name].to_text(current_name)
for split_record in current_record.split("\n"): # Split the records on the newline
record_array.append({'rr_name' : split_record.split(" ")[0],
'rr_ttl' : split_record.split(" ")[1],
'rr_class' : split_record.split(" ")[2],
'rr_type' : split_record.split(" ")[3],
'rr_data' : split_record.split(" ")[4]})
for split_record in current_record.split("\n"):
current_record = split_record.split(" ")
rr_dict = {}
rr_dict["rr_name"] = current_record[0]
rr_dict["rr_ttl"] = current_record[1]
rr_dict["rr_class"] = current_record[2]
rr_dict["rr_type"] = current_record[3]
rr_dict["rr_data"] = current_record[4]
record_array.append(rr_dict)
return record_array

View File

@ -1,6 +1,5 @@
from django.shortcuts import redirect, render
from binder import exceptions, forms, helpers, models
from django.shortcuts import redirect, render
import re
@ -8,12 +7,12 @@ RE_UNICODEARRAY = re.compile(r"u'(.*?)'")
def home_index(request):
""" List the main index page for Binder. """
return render(request, 'index.htm')
return render(request, "index.htm")
def view_server_list(request):
""" List the DNS servers configured in the Django DB. """
server_list = models.BindServer.objects.all().order_by('hostname')
return render(request, 'bcommon/list_servers.htm',
""" List the DNS servers configured in the database. """
server_list = models.BindServer.objects.all().order_by("hostname")
return render(request, "bcommon/list_servers.htm",
{ "server_list" : server_list})
def view_server_zones(request, dns_server):
@ -24,35 +23,35 @@ def view_server_zones(request, dns_server):
this_server = models.BindServer.objects.get(hostname=dns_server)
zone_array = this_server.list_zones()
except models.BindServer.DoesNotExist, err:
errors = "Configured server does not exist: %s" % dns_server
errors = "There is no configured server by that name: %s" % dns_server
except exceptions.ZoneException, err:
errors = "Unable to list server zones. Error: %s" % err
return render(request, 'bcommon/list_server_zones.htm',
return render(request, "bcommon/list_server_zones.htm",
{ "errors" : errors,
"dns_server" : dns_server,
"zone_array" : zone_array})
def view_zone_records(request, dns_server, zone_name):
""" Display the list of records for a a particular zone."""
""" Display the list of records for a particular zone. """
# Have we tried here to transfer for a zone that does not exist?
# Try with a transfer key and without.
try:
this_server = models.BindServer.objects.get(hostname=dns_server)
zone_array = this_server.list_zone_records(zone_name)
except exceptions.TransferException, err:
return render(request, 'bcommon/list_zone.htm',
{ 'errors' : err,
'zone_name' : zone_name})
return render(request, "bcommon/list_zone.htm",
{ "errors" : err,
"zone_name" : zone_name})
return render(request, 'bcommon/list_zone.htm',
{ 'zone_array' : zone_array,
'dns_server' : dns_server,
'zone_name' : zone_name})
return render(request, "bcommon/list_zone.htm",
{ "zone_array" : zone_array,
"dns_server" : dns_server,
"zone_name" : zone_name})
def view_add_record(request, dns_server, zone_name):
""" View to provide form to add a DNS record. """
print "keys: %r" % models.Key.objects.all()
form = forms.FormAddRecord()
return render(request, 'bcommon/add_record_form.htm',
return render(request, "bcommon/add_record_form.htm",
{ "dns_server" : dns_server,
"zone_name" : zone_name,
"tsig_keys" : models.Key.objects.all() })
@ -61,21 +60,22 @@ def view_add_record_result(request):
""" Process the input given to add a DNS record. """
errors = ""
if request.method == "GET":
return redirect('/')
return redirect("/")
form = forms.FormAddRecord(request.POST)
if form.is_valid():
cd = form.cleaned_data
form_cleaned = form.cleaned_data
try:
add_record_response = helpers.add_record(cd)
add_record_response = helpers.add_record(form_cleaned)
except exceptions.RecordException, err:
# TODO: Start using this exception.
errors = err
return render(request, 'bcommon/response_result.htm',
return render(request, "bcommon/response_result.htm",
{ "errors" : errors,
"response" : add_record_response })
return render(request, 'bcommon/add_record_form.htm',
return render(request, "bcommon/add_record_form.htm",
{ "dns_server" : request.POST["dns_server"],
"zone_name" : request.POST["zone_name"],
"form_errors" : form.errors,
@ -92,25 +92,22 @@ def view_add_cname_record(request, dns_server, zone_name, record_name):
def view_add_cname_result(request):
""" Process input on the CNAME form and provide a response."""
if request.method == "GET":
return redirect('/')
return redirect("/")
form = forms.FormAddCnameRecord(request.POST)
errors = ""
form = forms.FormAddCnameRecord(request.POST)
if form.is_valid():
cd = form.cleaned_data
try:
add_cname_response = helpers.add_cname_record(
str(cd["dns_server"]),
str(cd["zone_name"]),
str(cd["originating_record"]),
str(cd["cname"]),
str(cd["ttl"]),
str(cd["key_name"]))
except:
print "hit exception in view_add_cname_result"
add_cname_response = helpers.add_cname_record(
str(cd["dns_server"]),
str(cd["zone_name"]),
str(cd["originating_record"]),
str(cd["cname"]),
str(cd["ttl"]),
str(cd["key_name"]))
return render(request, 'bcommon/response_result.htm',
{'response' : add_cname_response })
return render(request, "bcommon/response_result.htm",
{"response" : add_cname_response })
return render(request, "bcommon/add_cname_record_form.htm",
{ "dns_server" : request.POST["dns_server"],
@ -123,40 +120,37 @@ def view_add_cname_result(request):
def view_delete_record(request):
"""Provide the initial form for deleting records."""
""" Provide the initial form for deleting records. """
if request.method == "GET":
# Return home. You shouldn't trying to directly acces
# the url for deleting records.
return redirect('/')
return redirect("/")
dns_server = request.POST['dns_server']
zone_name = request.POST['zone_name']
rr_array = request.POST.getlist('rr_array')
dns_server = request.POST["dns_server"]
zone_name = request.POST["zone_name"]
rr_array = request.POST.getlist("rr_array")
return render(request, 'bcommon/delete_record_initial.htm',
{ 'dns_server' : dns_server,
'zone_name' : zone_name,
'rr_array' : rr_array,
'tsig_keys' : models.Key.objects.all() })
return render(request, "bcommon/delete_record_initial.htm",
{ "dns_server" : dns_server,
"zone_name" : zone_name,
"rr_array" : rr_array,
"tsig_keys" : models.Key.objects.all() })
def view_delete_result(request):
""" View that deletes records and returns the response."""
""" View that deletes records and returns the response. """
if request.method == "GET":
# Return home. You shouldn't trying to directly access
# the url for deleting records.
return redirect('/')
return redirect("/")
# What seems like an ugly hack to get around the fact
# that the array comes back as Unicode values.
rr_unicode_array = request.POST.getlist('rr_array')[0]
# TODO: Can we make this cleaner?
rr_unicode_array = request.POST.getlist("rr_array")[0]
rr_items = RE_UNICODEARRAY.findall(rr_unicode_array)
try:
delete_result = helpers.delete_record(request.POST, rr_items)
except exceptions.RecordException, err:
return render(request, 'bcommon/response_result.htm.htm',
{ "errors" : err })
delete_result = helpers.delete_record(request.POST, rr_items)
return render(request, 'bcommon/response_result.htm',
{ 'response' : delete_result })
return render(request, "bcommon/response_result.htm",
{ "response" : delete_result })