|
|
|
@ -6,13 +6,15 @@ import base64 |
|
|
|
|
import platform |
|
|
|
|
import pkg_resources |
|
|
|
|
import time |
|
|
|
|
import sys |
|
|
|
|
import struct |
|
|
|
|
import io |
|
|
|
|
|
|
|
|
|
# TODO: Accept explicit requests for DNAME? |
|
|
|
|
# TODO: DANE/TLSA record type. Not yet in DNS Python release |
|
|
|
|
# (committed in the upstream git repository) so not easy... |
|
|
|
|
|
|
|
|
|
import Answer |
|
|
|
|
from . import Answer |
|
|
|
|
|
|
|
|
|
def to_hexstring(str): |
|
|
|
|
result = "" |
|
|
|
@ -24,7 +26,7 @@ def keylength(alg, key): |
|
|
|
|
""" Returns the length in bits """ |
|
|
|
|
if alg == 5 or alg == 7 or alg == 8 or alg == 10: |
|
|
|
|
# RSA, RFC 3110 |
|
|
|
|
firstbyte = struct.unpack("B", key[0])[0] |
|
|
|
|
firstbyte = key[0].to_bytes(1, byteorder=sys.byteorder)[0] |
|
|
|
|
if firstbyte > 0: |
|
|
|
|
exponentlength = firstbyte + 1 |
|
|
|
|
else: |
|
|
|
@ -35,7 +37,7 @@ def keylength(alg, key): |
|
|
|
|
# the format of ECDSA or GOST keys. |
|
|
|
|
return len(key)*8 |
|
|
|
|
|
|
|
|
|
class Formatter(): |
|
|
|
|
class Formatter(object): |
|
|
|
|
""" This ia the base class for the various Formatters. A formatter |
|
|
|
|
takes a "DNS answer" object and format it for a given output |
|
|
|
|
format (JSON, XML, etc). Implementing a new format means deriving |
|
|
|
@ -71,7 +73,7 @@ class TextFormatter(Formatter): |
|
|
|
|
qclass_text = ", class %s" % qclass |
|
|
|
|
else: |
|
|
|
|
qclass_text = "" |
|
|
|
|
self.output += "Query for: %s, type %s%s\n" % (self.domain.encode(querier.encoding), |
|
|
|
|
self.output += "Query for: %s, type %s%s\n" % (self.domain, |
|
|
|
|
qtype, qclass_text) |
|
|
|
|
str_flags = "" |
|
|
|
|
if flags & dns.flags.AD: |
|
|
|
@ -163,7 +165,7 @@ class TextFormatter(Formatter): |
|
|
|
|
platform.python_version(), platform.system()) |
|
|
|
|
|
|
|
|
|
def result(self, querier): |
|
|
|
|
return self.output |
|
|
|
|
return self.output.encode() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZONE FILE |
|
|
|
@ -175,7 +177,7 @@ class ZoneFormatter(Formatter): |
|
|
|
|
qclass_text = ", class %s" % qclass |
|
|
|
|
else: |
|
|
|
|
qclass_text = "" |
|
|
|
|
self.output += "; Question: %s, type %s%s\n" % (self.domain.encode(querier.encoding), |
|
|
|
|
self.output += "; Question: %s, type %s%s\n" % (self.domain, |
|
|
|
|
qtype, qclass_text) |
|
|
|
|
str_flags = "" |
|
|
|
|
if flags & dns.flags.AD: |
|
|
|
@ -194,7 +196,7 @@ class ZoneFormatter(Formatter): |
|
|
|
|
for rdata in rrset: |
|
|
|
|
# TODO: do not hardwire the class |
|
|
|
|
if rdata.rdtype != dns.rdatatype.RRSIG: |
|
|
|
|
self.output += "%s\tIN\t" % answer.qname # TODO: do not repeat the name if there is a RRset |
|
|
|
|
self.output += "%s\tIN\t" % answer.qname.decode() # TODO: do not repeat the name if there is a RRset |
|
|
|
|
# TODO: it could use some refactoring: most (but _not all_) of types |
|
|
|
|
# use the same code. |
|
|
|
|
if rdata.rdtype == dns.rdatatype.A: |
|
|
|
@ -259,7 +261,7 @@ class ZoneFormatter(Formatter): |
|
|
|
|
platform.python_version(), platform.system()) |
|
|
|
|
|
|
|
|
|
def result(self, querier): |
|
|
|
|
return self.output |
|
|
|
|
return self.output.encode() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# JSON |
|
|
|
@ -371,7 +373,7 @@ class JsonFormatter(Formatter): |
|
|
|
|
except AttributeError: # total_seconds appeared only with Python 2.7 |
|
|
|
|
delay = querier.delay |
|
|
|
|
duration = (delay.days*86400) + delay.seconds + \ |
|
|
|
|
(float(delay.microseconds)/1000000.0) |
|
|
|
|
(old_div(float(delay.microseconds),1000000.0)) |
|
|
|
|
self.object['Query'] = {'Server': answer.nameserver, |
|
|
|
|
'Time': time.strftime("%Y-%m-%d %H:%M:%SZ", |
|
|
|
|
time.gmtime(time.time())), |
|
|
|
@ -385,7 +387,7 @@ class JsonFormatter(Formatter): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def result(self, querier): |
|
|
|
|
return json.dumps(self.object, indent=True) + "\n" |
|
|
|
|
return (json.dumps(self.object, indent=True) + "\n").encode() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# XML |
|
|
|
@ -506,7 +508,7 @@ class XmlFormatter(Formatter): |
|
|
|
|
except AttributeError: # total_seconds appeared only with Python 2.7 |
|
|
|
|
delay = querier.delay |
|
|
|
|
duration = (delay.days*86400) + delay.seconds + \ |
|
|
|
|
(float(delay.microseconds)/1000000.0) |
|
|
|
|
(old_div(float(delay.microseconds),1000000.0)) |
|
|
|
|
self.context.addGlobal ("duration", duration) |
|
|
|
|
self.context.addGlobal ("time", time.strftime("%Y-%m-%d %H:%M:%SZ", |
|
|
|
|
time.gmtime(time.time()))) |
|
|
|
@ -541,7 +543,7 @@ class XmlFormatter(Formatter): |
|
|
|
|
self.acontext.addGlobal ("type", dns.rdatatype.to_text(rrset.rdtype)) |
|
|
|
|
for rdata in rrset: |
|
|
|
|
icontext = simpleTALES.Context(allowPythonPath=False) |
|
|
|
|
iresult = simpleTALUtils.FastStringOutput() |
|
|
|
|
iresult = io.StringIO() |
|
|
|
|
if rdata.rdtype == dns.rdatatype.A or rdata.rdtype == dns.rdatatype.AAAA: |
|
|
|
|
icontext.addGlobal ("address", rdata.address) |
|
|
|
|
if rdata.rdtype == dns.rdatatype.A: |
|
|
|
@ -617,7 +619,7 @@ class XmlFormatter(Formatter): |
|
|
|
|
icontext.addGlobal ("services", rdata.service) |
|
|
|
|
icontext.addGlobal ("order", rdata.order) |
|
|
|
|
icontext.addGlobal ("preference", rdata.preference) |
|
|
|
|
regexp = unicode(rdata.regexp, "UTF-8") |
|
|
|
|
regexp = str(rdata.regexp, "UTF-8") |
|
|
|
|
icontext.addGlobal ("regexp", |
|
|
|
|
regexp) |
|
|
|
|
# Yes, there is Unicode in NAPTRs, see |
|
|
|
@ -631,7 +633,7 @@ class XmlFormatter(Formatter): |
|
|
|
|
# Yes, some people add Unicode in TXT records, |
|
|
|
|
# see mailclub.tel for instance. We assume |
|
|
|
|
# UTF-8 |
|
|
|
|
text = unicode(" ".join(rdata.strings), "UTF-8") |
|
|
|
|
text = str(" ".join(rdata.strings), "UTF-8") |
|
|
|
|
icontext.addGlobal ("text", text) |
|
|
|
|
self.txt_template.expand (icontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
@ -689,26 +691,26 @@ class XmlFormatter(Formatter): |
|
|
|
|
self.unknown_template.expand (icontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
records.append(unicode(iresult.getvalue(), querier.encoding)) |
|
|
|
|
records.append(iresult.getvalue()) |
|
|
|
|
else: |
|
|
|
|
pass # TODO what to send back when no data for this QTYPE? |
|
|
|
|
if records: |
|
|
|
|
self.acontext.addGlobal ("records", records) |
|
|
|
|
self.acontext.addGlobal ("ttl", rrset.ttl) |
|
|
|
|
iresult = simpleTALUtils.FastStringOutput() |
|
|
|
|
iresult = io.StringIO() |
|
|
|
|
self.set_template.expand (self.acontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
self.rrsets.append(unicode(iresult.getvalue(), querier.encoding)) |
|
|
|
|
self.rrsets.append(iresult.getvalue()) |
|
|
|
|
else: |
|
|
|
|
self.rrsets = None |
|
|
|
|
|
|
|
|
|
def result(self, querier): |
|
|
|
|
result = simpleTALUtils.FastStringOutput() |
|
|
|
|
result = io.StringIO() |
|
|
|
|
self.context.addGlobal("rrsets", self.rrsets) |
|
|
|
|
self.xml_template.expand (self.context, result, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
return result.getvalue() |
|
|
|
|
return result.getvalue().encode() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# HTML |
|
|
|
@ -829,10 +831,10 @@ class HtmlFormatter(Formatter): |
|
|
|
|
|
|
|
|
|
def pretty_duration(self, duration): |
|
|
|
|
""" duration is in seconds """ |
|
|
|
|
weeks = duration/(86400*7) |
|
|
|
|
days = (duration-(86400*7*weeks))/86400 |
|
|
|
|
hours = (duration-(86400*7*weeks)-(86400*days))/3600 |
|
|
|
|
minutes = (duration-(86400*7*weeks)-(86400*days)-(3600*hours))/60 |
|
|
|
|
weeks = old_div(duration,(86400*7)) |
|
|
|
|
days = old_div((duration-(86400*7*weeks)),86400) |
|
|
|
|
hours = old_div((duration-(86400*7*weeks)-(86400*days)),3600) |
|
|
|
|
minutes = old_div((duration-(86400*7*weeks)-(86400*days)-(3600*hours)),60) |
|
|
|
|
seconds = duration-(86400*7*weeks)-(86400*days)-(3600*hours)-(60*minutes) |
|
|
|
|
result = "" |
|
|
|
|
empty_result = True |
|
|
|
@ -919,7 +921,7 @@ class HtmlFormatter(Formatter): |
|
|
|
|
self.context.addGlobal("description_html", querier.description_html) |
|
|
|
|
elif querier.description: |
|
|
|
|
self.context.addGlobal("description", querier.description) |
|
|
|
|
iresult = simpleTALUtils.FastStringOutput() |
|
|
|
|
iresult = io.StringIO() |
|
|
|
|
icontext = simpleTALES.Context(allowPythonPath=False) |
|
|
|
|
icontext.addGlobal("pyversion", platform.python_implementation() + " " + |
|
|
|
|
platform.python_version() + " on " + platform.system()) |
|
|
|
@ -928,7 +930,7 @@ class HtmlFormatter(Formatter): |
|
|
|
|
self.version_template.expand (icontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
self.context.addGlobal("versions", unicode(iresult.getvalue(), querier.encoding)) |
|
|
|
|
self.context.addGlobal("versions", iresult.getvalue()) |
|
|
|
|
str_flags = "" |
|
|
|
|
if flags & dns.flags.AD: |
|
|
|
|
str_flags += "/ Authentic Data " |
|
|
|
@ -944,7 +946,7 @@ class HtmlFormatter(Formatter): |
|
|
|
|
for rrset in answer.answer: |
|
|
|
|
records = [] |
|
|
|
|
for rdata in rrset: |
|
|
|
|
iresult = simpleTALUtils.FastStringOutput() |
|
|
|
|
iresult = io.StringIO() |
|
|
|
|
if rdata.rdtype == dns.rdatatype.A or rdata.rdtype == dns.rdatatype.AAAA: |
|
|
|
|
icontext.addGlobal ("address", rdata.address) |
|
|
|
|
icontext.addGlobal ("path", self.link_of(rdata.address, |
|
|
|
@ -1003,7 +1005,7 @@ class HtmlFormatter(Formatter): |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
elif rdata.rdtype == dns.rdatatype.TXT: |
|
|
|
|
text = unicode(" ".join(rdata.strings), "UTF-8") |
|
|
|
|
text = str(" ".join(rdata.strings), "UTF-8") |
|
|
|
|
icontext.addGlobal ("text", text) |
|
|
|
|
self.txt_template.expand (icontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
@ -1088,7 +1090,7 @@ class HtmlFormatter(Formatter): |
|
|
|
|
icontext.addGlobal ("order", rdata.order) |
|
|
|
|
icontext.addGlobal ("preference", rdata.preference) |
|
|
|
|
icontext.addGlobal ("services", rdata.service) |
|
|
|
|
icontext.addGlobal ("regexp", unicode(rdata.regexp, |
|
|
|
|
icontext.addGlobal ("regexp", str(rdata.regexp, |
|
|
|
|
"UTF-8")) # UTF-8 rdata is found in the wild |
|
|
|
|
icontext.addGlobal ("replacement", rdata.replacement) |
|
|
|
|
self.naptr_template.expand (icontext, iresult, |
|
|
|
@ -1099,16 +1101,16 @@ class HtmlFormatter(Formatter): |
|
|
|
|
self.unknown_template.expand (icontext, iresult, |
|
|
|
|
suppressXMLDeclaration=True, |
|
|
|
|
outputEncoding=querier.encoding) |
|
|
|
|
records.append(unicode(iresult.getvalue(), querier.encoding)) |
|
|
|
|
records.append(iresult.getvalue()) |
|
|
|
|
self.rrsets.append({'ttl': self.pretty_duration(rrset.ttl), |
|
|
|
|
'records': records}) |
|
|
|
|
else: |
|
|
|
|
self.rrsets = None |
|
|
|
|
|
|
|
|
|
def result(self, querier): |
|
|
|
|
result = simpleTALUtils.FastStringOutput() |
|
|
|
|
result = io.StringIO() |
|
|
|
|
self.context.addGlobal("rrsets", self.rrsets) |
|
|
|
|
self.template.expand (self.context, result, |
|
|
|
|
outputEncoding=querier.encoding, |
|
|
|
|
docType='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">') |
|
|
|
|
return (result.getvalue() + "\n") |
|
|
|
|
return ((result.getvalue() + "\n").encode()) |
|
|
|
|