git-svn-id: https://linkchecker.svn.sourceforge.net/svnroot/linkchecker/trunk/linkchecker@1426 e7d03fd6-7b0d-0410-9947-9c21f3af8025
This commit is contained in:
calvin 2004-08-16 19:28:42 +00:00
parent bfa70accff
commit e25ea13fa7
109 changed files with 31075 additions and 0 deletions

283
config/reindent.py Normal file
View file

@ -0,0 +1,283 @@
#! /usr/bin/python2.3
# Released to the public domain, by Tim Peters, 03 October 2000.
"""reindent [-d][-r][-v] [ path ... ]
-d Dry run. Analyze, but don't make any changes to, files.
-r Recurse. Search for all .py files in subdirectories too.
-v Verbose. Print informative msgs; else no output.
Change Python (.py) files to use 4-space indents and no hard tab characters.
Also trim excess spaces and tabs from ends of lines, and remove empty lines
at the end of files. Also ensure the last line ends with a newline.
If no paths are given on the command line, reindent operates as a filter,
reading a single source file from standard input and writing the transformed
source to standard output. In this case, the -d, -r and -v flags are
ignored.
You can pass one or more file and/or directory paths. When a directory
path, all .py files within the directory will be examined, and, if the -r
option is given, likewise recursively for subdirectories.
If output is not to standard output, reindent overwrites files in place,
renaming the originals with a .bak extension. If it finds nothing to
change, the file is left alone. If reindent does change a file, the changed
file is a fixed-point for future runs (i.e., running reindent on the
resulting .py file won't change it again).
The hard part of reindenting is figuring out what to do with comment
lines. So long as the input files get a clean bill of health from
tabnanny.py, reindent should do a good job.
"""
__version__ = "1"
import tokenize
import os
import sys
verbose = 0
recurse = 0
dryrun = 0
def errprint(*args):
sep = ""
for arg in args:
sys.stderr.write(sep + str(arg))
sep = " "
sys.stderr.write("\n")
def main():
import getopt
global verbose, recurse, dryrun
try:
opts, args = getopt.getopt(sys.argv[1:], "drv")
except getopt.error, msg:
errprint(msg)
return
for o, a in opts:
if o == '-d':
dryrun += 1
elif o == '-r':
recurse += 1
elif o == '-v':
verbose += 1
if not args:
r = Reindenter(sys.stdin)
r.run()
r.write(sys.stdout)
return
for arg in args:
check(arg)
def check(file):
if os.path.isdir(file) and not os.path.islink(file):
if verbose:
print "listing directory", file
names = os.listdir(file)
for name in names:
fullname = os.path.join(file, name)
if ((recurse and os.path.isdir(fullname) and
not os.path.islink(fullname))
or name.lower().endswith(".py")):
check(fullname)
return
if verbose:
print "checking", file, "...",
try:
f = open(file)
except IOError, msg:
errprint("%s: I/O Error: %s" % (file, str(msg)))
return
r = Reindenter(f)
f.close()
if r.run():
if verbose:
print "changed."
if dryrun:
print "But this is a dry run, so leaving it alone."
if not dryrun:
bak = file + ".bak"
if os.path.exists(bak):
os.remove(bak)
os.rename(file, bak)
if verbose:
print "renamed", file, "to", bak
f = open(file, "w")
r.write(f)
f.close()
if verbose:
print "wrote new", file
else:
if verbose:
print "unchanged."
def _rstrip(line, JUNK='\n \t'):
"""Return line stripped of trailing spaces, tabs, newlines.
Note that line.rstrip() instead also strips sundry control characters,
but at least one known Emacs user expects to keep junk like that, not
mentioning Barry by name or anything <wink>.
"""
i = len(line)
while i > 0 and line[i-1] in JUNK:
i -= 1
return line[:i]
class Reindenter:
def __init__(self, f):
self.find_stmt = 1 # next token begins a fresh stmt?
self.level = 0 # current indent level
# Raw file lines.
self.raw = f.readlines()
# File lines, rstripped & tab-expanded. Dummy at start is so
# that we can use tokenize's 1-based line numbering easily.
# Note that a line is all-blank iff it's "\n".
self.lines = [_rstrip(line).expandtabs() + "\n"
for line in self.raw]
self.lines.insert(0, None)
self.index = 1 # index into self.lines of next line
# List of (lineno, indentlevel) pairs, one for each stmt and
# comment line. indentlevel is -1 for comment lines, as a
# signal that tokenize doesn't know what to do about them;
# indeed, they're our headache!
self.stats = []
def run(self):
tokenize.tokenize(self.getline, self.tokeneater)
# Remove trailing empty lines.
lines = self.lines
while lines and lines[-1] == "\n":
lines.pop()
# Sentinel.
stats = self.stats
stats.append((len(lines), 0))
# Map count of leading spaces to # we want.
have2want = {}
# Program after transformation.
after = self.after = []
# Copy over initial empty lines -- there's nothing to do until
# we see a line with *something* on it.
i = stats[0][0]
after.extend(lines[1:i])
for i in range(len(stats)-1):
thisstmt, thislevel = stats[i]
nextstmt = stats[i+1][0]
have = getlspace(lines[thisstmt])
want = thislevel * 4
if want < 0:
# A comment line.
if have:
# An indented comment line. If we saw the same
# indentation before, reuse what it most recently
# mapped to.
want = have2want.get(have, -1)
if want < 0:
# Then it probably belongs to the next real stmt.
for j in xrange(i+1, len(stats)-1):
jline, jlevel = stats[j]
if jlevel >= 0:
if have == getlspace(lines[jline]):
want = jlevel * 4
break
if want < 0: # Maybe it's a hanging
# comment like this one,
# in which case we should shift it like its base
# line got shifted.
for j in xrange(i-1, -1, -1):
jline, jlevel = stats[j]
if jlevel >= 0:
want = have + getlspace(after[jline-1]) - \
getlspace(lines[jline])
break
if want < 0:
# Still no luck -- leave it alone.
want = have
else:
want = 0
assert want >= 0
have2want[have] = want
diff = want - have
if diff == 0 or have == 0:
after.extend(lines[thisstmt:nextstmt])
else:
for line in lines[thisstmt:nextstmt]:
if diff > 0:
if line == "\n":
after.append(line)
else:
after.append(" " * diff + line)
else:
remove = min(getlspace(line), -diff)
after.append(line[remove:])
return self.raw != self.after
def write(self, f):
f.writelines(self.after)
# Line-getter for tokenize.
def getline(self):
if self.index >= len(self.lines):
line = ""
else:
line = self.lines[self.index]
self.index += 1
return line
# Line-eater for tokenize.
def tokeneater(self, type, token, (sline, scol), end, line,
INDENT=tokenize.INDENT,
DEDENT=tokenize.DEDENT,
NEWLINE=tokenize.NEWLINE,
COMMENT=tokenize.COMMENT,
NL=tokenize.NL):
if type == NEWLINE:
# A program statement, or ENDMARKER, will eventually follow,
# after some (possibly empty) run of tokens of the form
# (NL | COMMENT)* (INDENT | DEDENT+)?
self.find_stmt = 1
elif type == INDENT:
self.find_stmt = 1
self.level += 1
elif type == DEDENT:
self.find_stmt = 1
self.level -= 1
elif type == COMMENT:
if self.find_stmt:
self.stats.append((sline, -1))
# but we're still looking for a new stmt, so leave
# find_stmt alone
elif type == NL:
pass
elif self.find_stmt:
# This is the first "real token" following a NEWLINE, so it
# must be the first token of the next program statement, or an
# ENDMARKER.
self.find_stmt = 0
if line: # not endmarker
self.stats.append((sline, self.level))
# Count number of leading blanks.
def getlspace(line):
i, n = 0, len(line)
while i < n and line[i] == " ":
i += 1
return i
if __name__ == '__main__':
main()

View file

@ -0,0 +1,24 @@
# this parser needs flex >= 2.5.xx from http://lex.sf.net/
# for reentrant bison parser support!
FLEX=flex
PYVER=2.3
PYTHON=python$(PYVER)
all: htmllex.c htmlparse.c
%.o: %.c
gcc -g -O3 -Wall -pedantic -Wstrict-prototypes -fPIC -I. -I/usr/include/$(PYTHON) -c $< -o $@
htmlparse.h htmlparse.c: htmlparse.y htmlsax.h
bison htmlparse.y
htmllex.l: htmlparse.h
htmllex.c: htmllex.l htmlsax.h
$(FLEX) htmllex.l
clean:
rm -f htmlparse.c htmlparse.h htmllex.c *.o *.so *.pyc *.pyo *.output
splint:
splint -initallelements +posixlib -I/usr/include/linux -I. -I/usr/include/$(PYTHON) htmllex.c | less

View file

@ -0,0 +1,114 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2000-2004 Bastian Kleineidam
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""Fast HTML parser module written in C with the following features:
1. Reentrant
As soon as any HTML string data is available, we try to feed it
to the HTML parser. This means that the parser has to scan possible
incomplete data, recognizing as much as it can. Incomplete trailing
data is saved for subsequent callsm, or it is just flushed into the
output buffer with the flush() function.
A reset() brings the parser back to its initial state, throwing away all
buffered data.
2. Coping with HTML syntax errors
The parser recognizes as much as it can and passes the rest
of the data as TEXT tokens.
The scanner only passes complete recognized HTML syntax elements to
the parser. Invalid syntax elements are passed as TEXT. This way we do
not need the bison error recovery.
Incomplete data is rescanned the next time the parser calls yylex() or
when it is being flush()ed.
The following syntax errors will be recognized correctly:
a) missing quotes around attribute values
b) "</...>" end tags in script modus
c) missing ">" in tags
d) invalid tag names
e) invalid characters inside tags or tag attributes
Additionally the parser has the following features:
a) NULL bytes are changed into spaces
b) <!-- ... --> inside a <script> or <style> are not treated as
comments but as DATA
3. Speed
The FLEX code has options to generate a large but fast scanner.
The parser ignores forbidden or unnecessary HTML end tags.
The parser converts tag and attribute names to lower case for easier
matching.
The parser quotes all attribute values.
Python memory management interface is used.
"""
import re
import htmlentitydefs
def _resolve_ascii_entity (mo):
"""Helper function for resolve_entities to resolve one &#XXX;
entity if it is an ASCII character. Else leave as is.
Input is a match object with a "num" group matched.
"""
# convert to number
ent = mo.group()
num = mo.group("num")
if ent.startswith('&#x'):
radix = 16
else:
radix = 10
num = int(num, radix)
# check 7-bit ASCII char range
if 0<=num<=127:
return chr(num)
# not in range
return ent
def resolve_ascii_entities (s):
"""resolve entities in 7-bit ASCII range to eliminate obfuscation"""
return re.sub(r'(?i)&#x?(?P<num>\d+);', _resolve_ascii_entity, s)
def _resolve_html_entity (mo):
"""resolve html entity, helper function for resolve_html_entities"""
return htmlentitydefs.entitydefs.get(mo.group("entity"), mo.group())
def resolve_html_entities (s):
"""resolve html entites in s and return result"""
return re.sub(r'(?i)&(?P<entity>[a-z]+);', _resolve_html_entity, s)
def resolve_entities (s):
"""resolve both html and 7-bit ASCII entites in s and return result"""
return resolve_html_entities(resolve_ascii_entities(s))
def strip_quotes (s):
"""remove possible double or single quotes"""
if len(s) >= 2 and \
((s.startswith("'") and s.endswith("'")) or \
(s.startswith('"') and s.endswith('"'))):
return s[1:-1]
return s

12138
linkcheck/HtmlParser/htmllex.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,101 @@
# -*- coding: iso-8859-1 -*-
"""Default handler classes"""
# Copyright (C) 2000-2004 Bastian Kleineidam
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import sys
class HtmlPrinter (object):
"""handles all functions by printing the function name and attributes"""
def __init__ (self, fd=sys.stdout):
"""write to given file descriptor"""
self.fd = fd
def _print (self, *attrs):
"""print function attributes"""
print >> self.fd, self.mem, attrs
def _errorfun (self, msg, name):
"""print msg to stderr with name prefix"""
print >> sys.stderr, name, msg
def error (self, msg):
"""signal a filter/parser error"""
self._errorfun(msg, "error:")
def warning (self, msg):
"""signal a filter/parser warning"""
self._errorfun(msg, "warning:")
def fatal_error (self, msg):
"""signal a fatal filter/parser error"""
self._errorfun(msg, "fatal error:")
def __getattr__ (self, name):
"""remember the func name"""
self.mem = name
return self._print
class HtmlPrettyPrinter (object):
"""Print out all parsed HTML data"""
def __init__ (self, fd=sys.stdout):
"""write to given file descriptor"""
self.fd = fd
def comment (self, data):
"""print comment"""
self.fd.write("<!--%s-->" % data)
def start_element (self, tag, attrs):
"""print start element"""
self.fd.write("<%s"%tag.replace("/", ""))
for key, val in attrs.iteritems():
if val is None:
self.fd.write(" %s"%key)
else:
self.fd.write(" %s=\"%s\"" % (key, quote_attrval(val)))
self.fd.write(">")
def end_element (self, tag):
"""print end element"""
self.fd.write("</%s>" % tag)
def doctype (self, data):
"""print document type"""
self.fd.write("<!DOCTYPE%s>" % data)
def pi (self, data):
"""print pi"""
self.fd.write("<?%s?>" % data)
def cdata (self, data):
"""print cdata"""
self.fd.write("<![CDATA[%s]]>"%data)
def characters (self, data):
"""print characters"""
self.fd.write(data)
def quote_attrval (s):
"""quote a HTML attribute to be able to wrap it in double quotes"""
s = s.replace('&', "&amp;")
s = s.replace('"', "&quot;")
return s

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
/* A Bison parser, made by GNU Bison 1.875a. */
/* Skeleton parser for Yacc-like parsing with Bison,
Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
/* As a special exception, when this file is copied by Bison into a
Bison output file, you may use that output file without restriction.
This special exception was added by the Free Software Foundation
in version 1.24 of Bison. */
/* Tokens. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
/* Put the tokens into the symbol table, so that GDB and other debuggers
know about them. */
enum yytokentype {
T_WAIT = 258,
T_ERROR = 259,
T_TEXT = 260,
T_ELEMENT_START = 261,
T_ELEMENT_START_END = 262,
T_ELEMENT_END = 263,
T_SCRIPT = 264,
T_STYLE = 265,
T_PI = 266,
T_COMMENT = 267,
T_CDATA = 268,
T_DOCTYPE = 269
};
#endif
#define T_WAIT 258
#define T_ERROR 259
#define T_TEXT 260
#define T_ELEMENT_START 261
#define T_ELEMENT_START_END 262
#define T_ELEMENT_END 263
#define T_SCRIPT 264
#define T_STYLE 265
#define T_PI 266
#define T_COMMENT 267
#define T_CDATA 268
#define T_DOCTYPE 269
#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
typedef int YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
# define YYSTYPE_IS_TRIVIAL 1
#endif

View file

@ -0,0 +1,852 @@
%{
/* Copyright (C) 2000-2004 Bastian Kleineidam
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Python module definition of a SAX html parser */
#include "htmlsax.h"
#include "structmember.h"
#include <string.h>
#include <stdio.h>
/* bison type definitions */
#define YYSTYPE PyObject*
#define YYPARSE_PARAM scanner
#define YYLEX_PARAM scanner
/* extern functions found in htmllex.l */
extern int yylex(YYSTYPE* yylvalp, void* scanner);
extern int htmllexInit (void** scanner, UserData* data);
extern int htmllexDebug (void** scanner, int debug);
extern int htmllexStart (void* scanner, UserData* data, const char* s, int slen);
extern int htmllexStop (void* scanner, UserData* data);
extern int htmllexDestroy (void* scanner);
extern void* yyget_extra(void*);
extern int yyget_lineno(void*);
#define YYERROR_VERBOSE 1
/* standard error reporting, indicating an internal error */
static int yyerror (char* msg) {
fprintf(stderr, "htmlsax: internal parse error: %s\n", msg);
return 0;
}
/* parser.resolve_entities */
static PyObject* resolve_entities;
static PyObject* list_dict;
/* macros for easier scanner state manipulation */
/* test whether tag does not need an HTML end tag */
#define NO_HTML_END_TAG(tag) !(strcmp(tag, "area")==0 || \
strcmp(tag, "base")==0 || \
strcmp(tag, "basefont")==0 || \
strcmp(tag, "br")==0 || \
strcmp(tag, "col")==0 || \
strcmp(tag, "frame")==0 || \
strcmp(tag, "hr")==0 || \
strcmp(tag, "img")==0 || \
strcmp(tag, "input")==0 || \
strcmp(tag, "isindex")==0 || \
strcmp(tag, "link")==0 || \
strcmp(tag, "meta")==0 || \
strcmp(tag, "param")==0)
/* clear buffer b, returning NULL on error */
#define CLEAR_BUF(b) \
b = PyMem_Resize(b, char, 1); \
if (b==NULL) return NULL; \
(b)[0] = '\0'
/* clear buffer b, returning NULL and decref self on error */
#define CLEAR_BUF_DECREF(self, b) \
b = PyMem_Resize(b, char, 1); \
if (b==NULL) { Py_DECREF(self); return NULL; } \
(b)[0] = '\0'
#define CHECK_ERROR(ud, label) \
if (ud->error && PyObject_HasAttrString(ud->handler, "error")==1) { \
callback = PyObject_GetAttrString(ud->handler, "error"); \
if (!callback) { error=1; goto label; } \
result = PyObject_CallFunction(callback, "O", ud->error); \
if (!result) { error=1; goto label; } \
}
/* generic callback macro */
#define CALLBACK(ud, attr, format, arg, label) \
if (PyObject_HasAttrString(ud->handler, attr)==1) { \
callback = PyObject_GetAttrString(ud->handler, attr); \
if (callback==NULL) { error=1; goto label; } \
result = PyObject_CallFunction(callback, format, arg); \
if (result==NULL) { error=1; goto label; } \
Py_DECREF(callback); \
Py_DECREF(result); \
callback=result=NULL; \
}
/* set old line and column */
#define SET_OLD_LINECOL \
ud->last_lineno = ud->lineno; \
ud->last_column = ud->column
/* parser type definition */
typedef struct {
PyObject_HEAD
PyObject* handler;
UserData* userData;
void* scanner;
} parser_object;
staticforward PyTypeObject parser_type;
/* use Pythons memory management */
#define malloc PyMem_Malloc
#define realloc PyMem_Realloc
#define free PyMem_Free
%}
/* parser options */
%verbose
%debug
%defines
%output="htmlparse.c"
%pure_parser
/* parser tokens */
%token T_WAIT
%token T_ERROR
%token T_TEXT
%token T_ELEMENT_START
%token T_ELEMENT_START_END
%token T_ELEMENT_END
%token T_SCRIPT
%token T_STYLE
%token T_PI
%token T_COMMENT
%token T_CDATA
%token T_DOCTYPE
/* the finish_ labels are for error recovery */
%%
elements: element {}
| elements element {}
;
element: T_WAIT { YYACCEPT; /* wait for more lexer input */ }
| T_ERROR
{
/* an error occured in the scanner, the python exception must be set */
UserData* ud = yyget_extra(scanner);
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
| T_ELEMENT_START
{
/* $1 is a PyTuple (<tag>, <attrs>)
<tag> is a PyString, <attrs> is a PyDict */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
PyObject* tag = PyTuple_GET_ITEM($1, 0);
PyObject* attrs = PyTuple_GET_ITEM($1, 1);
int error = 0;
if (!tag || !attrs) { error = 1; goto finish_start; }
if (PyObject_HasAttrString(ud->handler, "start_element")==1) {
callback = PyObject_GetAttrString(ud->handler, "start_element");
if (!callback) { error=1; goto finish_start; }
result = PyObject_CallFunction(callback, "OO", tag, attrs);
if (!result) { error=1; goto finish_start; }
Py_DECREF(callback);
Py_DECREF(result);
callback=result=NULL;
}
CHECK_ERROR(ud, finish_start);
finish_start:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_XDECREF(tag);
Py_XDECREF(attrs);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_ELEMENT_START_END
{
/* $1 is a PyTuple (<tag>, <attrs>)
<tag> is a PyString, <attrs> is a PyDict */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
PyObject* tag = PyTuple_GET_ITEM($1, 0);
PyObject* attrs = PyTuple_GET_ITEM($1, 1);
int error = 0;
char* tagname;
if (!tag || !attrs) { error = 1; goto finish_start_end; }
if (PyObject_HasAttrString(ud->handler, "start_element")==1) {
callback = PyObject_GetAttrString(ud->handler, "start_element");
if (!callback) { error=1; goto finish_start_end; }
result = PyObject_CallFunction(callback, "OO", tag, attrs);
if (!result) { error=1; goto finish_start_end; }
Py_DECREF(callback);
Py_DECREF(result);
callback=result=NULL;
}
tagname = PyString_AS_STRING(tag);
if (PyObject_HasAttrString(ud->handler, "end_element")==1 &&
NO_HTML_END_TAG(tagname)) {
callback = PyObject_GetAttrString(ud->handler, "end_element");
if (callback==NULL) { error=1; goto finish_start_end; }
result = PyObject_CallFunction(callback, "O", tag);
if (result==NULL) { error=1; goto finish_start_end; }
Py_DECREF(callback);
Py_DECREF(result);
callback=result=NULL;
}
CHECK_ERROR(ud, finish_start_end);
finish_start_end:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_XDECREF(tag);
Py_XDECREF(attrs);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_ELEMENT_END
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
char* tagname = PyString_AS_STRING($1);
if (PyObject_HasAttrString(ud->handler, "end_element")==1 &&
NO_HTML_END_TAG(tagname)) {
callback = PyObject_GetAttrString(ud->handler, "end_element");
if (callback==NULL) { error=1; goto finish_end; }
result = PyObject_CallFunction(callback, "O", $1);
if (result==NULL) { error=1; goto finish_end; }
Py_DECREF(callback);
Py_DECREF(result);
callback=result=NULL;
}
CHECK_ERROR(ud, finish_end);
finish_end:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_COMMENT
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "comment", "O", $1, finish_comment);
CHECK_ERROR(ud, finish_comment);
finish_comment:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_PI
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "pi", "O", $1, finish_pi);
CHECK_ERROR(ud, finish_pi);
finish_pi:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_CDATA
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "cdata", "O", $1, finish_cdata);
CHECK_ERROR(ud, finish_cdata);
finish_cdata:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_DOCTYPE
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "doctype", "O", $1, finish_doctype);
CHECK_ERROR(ud, finish_doctype);
finish_doctype:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_SCRIPT
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "characters", "O", $1, finish_script);
CALLBACK(ud, "end_element", "s", "script", finish_script);
CHECK_ERROR(ud, finish_script);
finish_script:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_STYLE
{
/* $1 is a PyString */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "characters", "O", $1, finish_style);
CALLBACK(ud, "end_element", "s", "style", finish_style);
CHECK_ERROR(ud, finish_style);
finish_style:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
| T_TEXT
{
/* $1 is a PyString */
/* Remember this is also called as a lexer error fallback */
UserData* ud = yyget_extra(scanner);
PyObject* callback = NULL;
PyObject* result = NULL;
int error = 0;
CALLBACK(ud, "characters", "O", $1, finish_characters);
CHECK_ERROR(ud, finish_characters);
finish_characters:
Py_XDECREF(ud->error);
ud->error = NULL;
Py_XDECREF(callback);
Py_XDECREF(result);
Py_DECREF($1);
if (error) {
PyErr_Fetch(&(ud->exc_type), &(ud->exc_val), &(ud->exc_tb));
YYABORT;
}
SET_OLD_LINECOL;
}
;
%%
/* disable python memory interface */
#undef malloc
#undef realloc
#undef free
/* create parser object */
static PyObject* parser_new (PyTypeObject* type, PyObject* args, PyObject* kwds) {
parser_object* self;
if ((self = (parser_object*) type->tp_alloc(type, 0)) == NULL)
{
return NULL;
}
Py_INCREF(Py_None);
self->handler = Py_None;
/* reset userData */
self->userData = PyMem_New(UserData, sizeof(UserData));
if (self->userData == NULL)
{
Py_DECREF(self);
return NULL;
}
self->userData->handler = self->handler;
self->userData->buf = NULL;
CLEAR_BUF_DECREF(self, self->userData->buf);
self->userData->nextpos = 0;
self->userData->bufpos = 0;
self->userData->pos = 0;
self->userData->column = 1;
self->userData->last_column = 1;
self->userData->lineno = 1;
self->userData->last_lineno = 1;
self->userData->tmp_buf = NULL;
CLEAR_BUF_DECREF(self, self->userData->tmp_buf);
self->userData->tmp_tag = self->userData->tmp_attrname =
self->userData->tmp_attrval = self->userData->tmp_attrs =
self->userData->lexbuf = NULL;
self->userData->resolve_entities = resolve_entities;
self->userData->list_dict = list_dict;
self->userData->exc_type = NULL;
self->userData->exc_val = NULL;
self->userData->exc_tb = NULL;
self->userData->error = NULL;
self->scanner = NULL;
if (htmllexInit(&(self->scanner), self->userData)!=0)
{
Py_DECREF(self);
return NULL;
}
return (PyObject*) self;
}
/* initialize parser object */
static int parser_init (parser_object* self, PyObject* args, PyObject* kwds) {
PyObject* handler = NULL;
static char *kwlist[] = {"handler", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &handler)) {
return -1;
}
if (handler==NULL) {
return 0;
}
Py_DECREF(self->handler);
Py_INCREF(handler);
self->handler = handler;
self->userData->handler = self->handler;
return 0;
}
/* traverse all used subobjects participating in reference cycles */
static int parser_traverse (parser_object* self, visitproc visit, void* arg) {
if (visit(self->handler, arg) < 0) {
return -1;
}
return 0;
}
/* clear all used subobjects participating in reference cycles */
static int parser_clear (parser_object* self) {
Py_XDECREF(self->handler);
self->handler = NULL;
self->userData->handler = NULL;
return 0;
}
/* free all allocated resources of parser object */
static void parser_dealloc (parser_object* self) {
htmllexDestroy(self->scanner);
parser_clear(self);
PyMem_Del(self->userData->buf);
PyMem_Del(self->userData->tmp_buf);
PyMem_Del(self->userData);
self->ob_type->tp_free((PyObject*)self);
}
/* feed a chunk of data to the parser */
static PyObject* parser_feed (parser_object* self, PyObject* args) {
/* set up the parse string */
int slen = 0;
char* s = NULL;
if (!PyArg_ParseTuple(args, "t#", &s, &slen)) {
PyErr_SetString(PyExc_TypeError, "string arg required");
return NULL;
}
/* parse */
if (htmllexStart(self->scanner, self->userData, s, slen)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not start scanner");
return NULL;
}
if (yyparse(self->scanner)!=0) {
if (self->userData->exc_type!=NULL) {
/* note: we give away these objects, so don't decref */
PyErr_Restore(self->userData->exc_type,
self->userData->exc_val,
self->userData->exc_tb);
}
htmllexStop(self->scanner, self->userData);
return NULL;
}
if (htmllexStop(self->scanner, self->userData)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not stop scanner");
return NULL;
}
Py_RETURN_NONE;
}
/* flush all parser buffers */
static PyObject* parser_flush (parser_object* self, PyObject* args) {
int res = 0;
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
/* reset parser variables */
CLEAR_BUF(self->userData->tmp_buf);
Py_XDECREF(self->userData->tmp_tag);
Py_XDECREF(self->userData->tmp_attrs);
Py_XDECREF(self->userData->tmp_attrval);
Py_XDECREF(self->userData->tmp_attrname);
self->userData->tmp_tag = self->userData->tmp_attrs =
self->userData->tmp_attrval = self->userData->tmp_attrname = NULL;
self->userData->bufpos = 0;
if (strlen(self->userData->buf)) {
/* XXX set line, col */
int error = 0;
PyObject* s = PyString_FromString(self->userData->buf);
PyObject* callback = NULL;
PyObject* result = NULL;
/* reset buffer */
CLEAR_BUF(self->userData->buf);
if (s==NULL) { error=1; goto finish_flush; }
if (PyObject_HasAttrString(self->handler, "characters")==1) {
callback = PyObject_GetAttrString(self->handler, "characters");
if (callback==NULL) { error=1; goto finish_flush; }
result = PyObject_CallFunction(callback, "O", s);
if (result==NULL) { error=1; goto finish_flush; }
}
finish_flush:
Py_XDECREF(callback);
Py_XDECREF(result);
Py_XDECREF(s);
if (error==1) {
return NULL;
}
}
if (htmllexDestroy(self->scanner)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not destroy scanner data");
return NULL;
}
self->scanner = NULL;
if (htmllexInit(&(self->scanner), self->userData)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not initialize scanner data");
return NULL;
}
return Py_BuildValue("i", res);
}
/* return the current parser line number */
static PyObject* parser_lineno (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
return Py_BuildValue("i", self->userData->lineno);
}
/* return the last parser line number */
static PyObject* parser_last_lineno (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
return Py_BuildValue("i", self->userData->last_lineno);
}
/* return the current parser column number */
static PyObject* parser_column (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
return Py_BuildValue("i", self->userData->column);
}
/* return the last parser column number */
static PyObject* parser_last_column (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
return Py_BuildValue("i", self->userData->last_column);
}
/* return the parser position in data stream */
static PyObject* parser_pos (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
return Py_BuildValue("i", self->userData->pos);
}
/* reset the parser. This will erase all buffered data! */
static PyObject* parser_reset (parser_object* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "")) {
PyErr_SetString(PyExc_TypeError, "no args required");
return NULL;
}
if (htmllexDestroy(self->scanner)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not destroy scanner data");
return NULL;
}
/* reset buffer */
CLEAR_BUF(self->userData->buf);
CLEAR_BUF(self->userData->tmp_buf);
self->userData->bufpos =
self->userData->pos =
self->userData->nextpos = 0;
self->userData->column =
self->userData->last_column =
self->userData->lineno =
self->userData->last_lineno = 1;
self->userData->tmp_tag = self->userData->tmp_attrs =
self->userData->tmp_attrval = self->userData->tmp_attrname = NULL;
self->scanner = NULL;
if (htmllexInit(&(self->scanner), self->userData)!=0) {
PyErr_SetString(PyExc_MemoryError, "could not initialize scanner data");
return NULL;
}
Py_RETURN_NONE;
}
/* set the debug level, if its >0, debugging is on, =0 means off */
static PyObject* parser_debug (parser_object* self, PyObject* args) {
int debug;
if (!PyArg_ParseTuple(args, "i", &debug)) {
return NULL;
}
yydebug = debug;
debug = htmllexDebug(&(self->scanner), debug);
return PyInt_FromLong((long)debug);
}
static PyObject* parser_gethandler (parser_object* self, void* closure) {
Py_INCREF(self->handler);
return self->handler;
}
static int parser_sethandler (parser_object* self, PyObject* value, void* closure) {
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete parser handler");
return -1;
}
Py_DECREF(self->handler);
Py_INCREF(value);
self->handler = value;
self->userData->handler = self->handler;
return 0;
}
/* type interface */
static PyMemberDef parser_members[] = {
{NULL} /* Sentinel */
};
static PyGetSetDef parser_getset[] = {
{"handler", (getter)parser_gethandler, (setter)parser_sethandler,
"handler object", NULL},
{NULL} /* Sentinel */
};
static PyMethodDef parser_methods[] = {
{"feed", (PyCFunction)parser_feed, METH_VARARGS, "feed data to parse incremental"},
{"reset", (PyCFunction)parser_reset, METH_VARARGS, "reset the parser (no flushing)"},
{"flush", (PyCFunction)parser_flush, METH_VARARGS, "flush parser buffers"},
{"debug", (PyCFunction)parser_debug, METH_VARARGS, "set debug level"},
{"lineno", (PyCFunction)parser_lineno, METH_VARARGS, "get the current line number"},
{"last_lineno", (PyCFunction)parser_last_lineno, METH_VARARGS, "get the last line number"},
{"column", (PyCFunction)parser_column, METH_VARARGS, "get the current column"},
{"last_column", (PyCFunction)parser_last_column, METH_VARARGS, "get the last column"},
{"pos", (PyCFunction)parser_pos, METH_VARARGS, "get the current scanner position"},
{NULL} /* Sentinel */
};
static PyTypeObject parser_type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
"linkcheck.HtmlParser.htmlsax.parser", /* tp_name */
sizeof(parser_object), /* tp_size */
0, /* tp_itemsize */
/* methods */
(destructor)parser_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_HAVE_GC, /* tp_flags */
"HTML parser object", /* tp_doc */
(traverseproc)parser_traverse, /* tp_traverse */
(inquiry)parser_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
parser_methods, /* tp_methods */
parser_members, /* tp_members */
parser_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)parser_init, /* tp_init */
0, /* tp_alloc */
parser_new, /* tp_new */
0, /* tp_free */
0, /* tp_is_gc */
0, /* tp_bases */
0, /* tp_mro */
0, /* tp_cache */
0, /* tp_subclasses */
0, /* tp_weaklist */
0, /* tp_del */
};
/* python module interface
"Create a new HTML parser object with handler (which may be None).\n"
"\n"
"Used callbacks (they don't have to be defined) of a handler are:\n"
"comment(data): <!--data-->\n"
"start_element(tag, attrs): <tag {attr1:value1,attr2:value2,..}>\n"
"end_element(tag): </tag>\n"
"doctype(data): <!DOCTYPE data?>\n"
"pi(name, data=None): <?name data?>\n"
"cdata(data): <![CDATA[data]]>\n"
"characters(data): data\n"
"\n"
"Additionally, there are error and warning callbacks:\n"
"error(msg)\n"
"warning(msg)\n"
"fatal_error(msg)\n"},
*/
static PyMethodDef htmlsax_methods[] = {
{NULL} /* Sentinel */
};
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
/* initialization of the htmlsax module */
PyMODINIT_FUNC inithtmlsax (void) {
PyObject* m = NULL;
if (PyType_Ready(&parser_type) < 0) {
return;
}
if ((m = Py_InitModule3("htmlsax", htmlsax_methods, "SAX HTML parser routines"))==NULL) {
return;
}
Py_INCREF(&parser_type);
if (PyModule_AddObject(m, "parser", (PyObject *)&parser_type)==-1) {
/* init error */
PyErr_Print();
}
if ((m = PyImport_ImportModule("linkcheck.HtmlParser"))==NULL) {
return;
}
if ((resolve_entities = PyObject_GetAttrString(m, "resolve_entities"))==NULL) {
Py_DECREF(m);
return;
}
Py_DECREF(m);
if ((m = PyImport_ImportModule("linkcheck.containers"))==NULL) {
return;
}
if ((list_dict = PyObject_GetAttrString(m, "ListDict"))==NULL) {
Py_DECREF(m);
return;
}
Py_DECREF(m);
}

View file

@ -0,0 +1,83 @@
/* Copyright (C) 2000-2004 Bastian Kleineidam
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef HTMLSAX_H
#define HTMLSAX_H
#include "Python.h"
/* require Python >= 2.3 */
#ifndef PY_VERSION_HEX
#error please install Python >= 2.3
#endif
#if PY_VERSION_HEX < 0x02030000
#error please install Python >= 2.3
#endif
/* this will be in Python 2.4 */
#ifndef Py_RETURN_NONE
#define Py_RETURN_NONE do {Py_INCREF(Py_None); return Py_None;} while (0)
#endif
/* user_data type for SAX calls */
typedef struct {
/* the Python SAX object to issue callbacks */
PyObject* handler;
/* Buffer to store still-to-be-scanned characters. After recognizing
* a complete syntax element, all data up to bufpos will be removed.
* Before scanning you should append new data to this buffer.
*/
char* buf;
/* current position in the buffer counting from zero */
unsigned int bufpos;
/* current position of next syntax element */
unsigned int nextpos;
/* position in the stream of data already seen, counting from zero */
unsigned int pos;
/* line counter, counting from one */
unsigned int lineno;
/* last value of line counter */
unsigned int last_lineno;
/* column counter, counting from zero */
unsigned int column;
/* last value of column counter */
unsigned int last_column;
/* input buffer of lexer, must be deleted when the parsing stops */
void* lexbuf;
/* temporary character buffer */
char* tmp_buf;
/* temporary HTML start or end tag name */
PyObject* tmp_tag;
/* temporary HTML start tag attribute name */
PyObject* tmp_attrname;
/* temporary HTML start tag attribute value */
PyObject* tmp_attrval;
/* temporary HTML start tag attribute list (a SortedDict) */
PyObject* tmp_attrs;
/* parser.resolve_entities */
PyObject* resolve_entities;
/* parser.SortedDict */
PyObject* list_dict;
/* stored Python exception (if error occurred in scanner) */
PyObject* exc_type;
PyObject* exc_val;
PyObject* exc_tb;
/* error string */
PyObject* error;
} UserData;
#endif

View file

@ -0,0 +1,52 @@
/*
* linux/lib/string.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#include <string.h>
#if !defined(HAVE_STRLCPY)
/**
* strlcpy - Copy a %NUL terminated string into a sized buffer
* @dst: Where to copy the string to
* @src: Where to copy the string from
* @size: size of destination buffer
*
* Compatible with *BSD: the result is always a valid
* NUL-terminated string that fits in the buffer (unless,
* of course, the buffer size is zero). It does not pad
* out the result like strncpy() does.
*/
size_t strlcpy (char *dst, const char *src, size_t size)
{
size_t ret = strlen(src);
if (size > 0) {
size_t len = (ret >= size) ? size-1 : ret;
memcpy(dst, src, len);
dst[len] = '\0';
}
return ret;
}
#endif /* !HAVE_STRLCPY */
#if !defined(HAVE_STRLCAT)
/**
* strlcat - Append a length-limited, %NUL-terminated string to another
* @dst: The string to be appended to
* @src: The string to append to it
* @size: The size of the destination buffer.
*/
size_t strlcat (char *dst, const char *src, size_t size)
{
size_t dsize = strlen(dst);
size_t len = strlen(src);
size_t res = dsize + len;
dst += dsize;
size -= dsize;
if (len >= size)
len = size-1;
memcpy(dst, src, len);
dst[len] = '\0';
return res;
}
#endif /* !HAVE_STRLCAT */

View file

@ -0,0 +1,14 @@
/*
* linux/lib/string.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#if !defined(HAVE_STRLCPY)
size_t strlcpy(char *dst, const char *src, size_t size);
#endif /* !HAVE_STRLCPY */
#if !defined(HAVE_STRLCAT)
size_t strlcat(char *dst, const char *src, size_t size);
#endif /* !HAVE_STRLCAT */

27
linkcheck/dns/ChangeLog Normal file
View file

@ -0,0 +1,27 @@
The following changes were made to the original dnspython 1.3.1
sources:
* moved everything into the linkcheck package
Changed: *.py
* moved unittests and adapted them for the schooltool.org test runner
Changed: tests/*.py
* fixed missing random import
Changed: renderer.py
* honor EnableDHCP flag in Windows registry config
Changed: resolver.py
* strip() lines from /etc/resolv.conf
Changed: resolver.py
* minor syntax cleanups
Changed: *.py
* added set of localhost addresses to resolver config
Changed: resolver.py
Added: IfConfig.py
* added search patters for domain names
Changed: resolver.py

50
linkcheck/dns/__init__.py Normal file
View file

@ -0,0 +1,50 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""dnspython DNS toolkit"""
__all__ = [
'dnssec',
'exception',
'flags',
'ifconfig',
'inet',
'ipv4',
'ipv6',
'message',
'name',
'namedict',
'node',
'opcode',
'query',
'rcode',
'rdata',
'rdataclass',
'rdataset',
'rdatatype',
'renderer',
'resolver',
'rrset',
'set',
'tokenizer',
'tsig',
'tsigkeyring',
'ttl',
'rdtypes',
'update',
'version',
'zone',
]

63
linkcheck/dns/dnssec.py Normal file
View file

@ -0,0 +1,63 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Common DNSSEC-related functions and constants."""
RSAMD5 = 1
DH = 2
DSA = 3
ECC = 4
INDIRECT = 252
PRIVATEDNS = 253
PRIVATEOID = 254
_algorithm_by_text = {
'RSAMD5' : RSAMD5,
'DH' : DH,
'DSA' : DSA,
'ECC' : ECC,
'INDIRECT' : INDIRECT,
'PRIVATEDNS' : PRIVATEDNS,
'PRIVATEOID' : PRIVATEOID,
}
# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse.
_algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()])
class UnknownAlgorithm(Exception):
"""Raised if an algorithm is unknown."""
pass
def algorithm_from_text(text):
"""Convert text into a DNSSEC algorithm value
@rtype: int"""
value = _algorithm_by_text.get(text.upper())
if value is None:
value = int(text)
return value
def algorithm_to_text(value):
"""Convert a DNSSEC algorithm value to text
@rtype: string"""
text = _algorithm_by_value.get(value)
if text is None:
text = str(value)
return text

View file

@ -0,0 +1,41 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Common DNS Exceptions."""
class DNSException(Exception):
"""Abstract base class shared by all dnspython exceptions."""
pass
class FormError(DNSException):
"""DNS message is malformed."""
pass
class SyntaxError(DNSException):
"""Text input is malformed."""
pass
class UnexpectedEnd(SyntaxError):
"""Raised if text input ends unexpectedly."""
pass
class TooBig(DNSException):
"""The message is too big."""
pass
class Timeout(DNSException):
"""The operation timed out."""
pass

107
linkcheck/dns/flags.py Normal file
View file

@ -0,0 +1,107 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Message Flags."""
# Standard DNS flags
QR = 0x8000
AA = 0x0400
TC = 0x0200
RD = 0x0100
RA = 0x0080
AD = 0x0020
CD = 0x0010
# EDNS flags
DO = 0x8000
_by_text = {
'QR' : QR,
'AA' : AA,
'TC' : TC,
'RD' : RD,
'RA' : RA,
'AD' : AD,
'CD' : CD
}
_edns_by_text = {
'DO' : DO
}
# We construct the inverse mappings programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mappings not to be true inverses.
_by_value = dict([(y, x) for x, y in _by_text.iteritems()])
_edns_by_value = dict([(y, x) for x, y in _edns_by_text.iteritems()])
def _order_flags(table):
order = list(table.iteritems())
order.sort()
order.reverse()
return order
_flags_order = _order_flags(_by_value)
_edns_flags_order = _order_flags(_edns_by_value)
def _from_text(text, table):
flags = 0
tokens = text.split()
for t in tokens:
flags = flags | table[t.upper()]
return flags
def _to_text(flags, table, order):
text_flags = []
for k, v in order:
if flags & k != 0:
text_flags.append(v)
return ' '.join(text_flags)
def from_text(text):
"""Convert a space-separated list of flag text values into a flags
value.
@rtype: int"""
return _from_text(text, _by_text)
def to_text(flags):
"""Convert a flags value into a space-separated list of flag text
values.
@rtype: string"""
return _to_text(flags, _by_value, _flags_order)
def edns_from_text(text):
"""Convert a space-separated list of EDNS flag text values into a EDNS
flags value.
@rtype: int"""
return _from_text(text, _edns_by_text)
def edns_to_text(flags):
"""Convert an EDNS flags value into a space-separated list of EDNS flag
text values.
@rtype: string"""
return _to_text(flags, _edns_by_value, _edns_flags_order)

104
linkcheck/dns/ifconfig.py Normal file
View file

@ -0,0 +1,104 @@
# -*- coding: iso-8859-1 -*-
"""from http://twistedmatrix.com/wiki/python/IfConfig
"""
import socket
import array
import fcntl
import os
import struct
import re
import linkcheck.log
class IfConfig (object):
"""Access to socket interfaces"""
SIOCGIFNAME = 0x8910
SIOCGIFCONF = 0x8912
SIOCGIFFLAGS = 0x8913
SIOCGIFADDR = 0x8915
SIOCGIFBRDADDR = 0x8919
SIOCGIFNETMASK = 0x891b
SIOCGIFCOUNT = 0x8938
IFF_UP = 0x1 # Interface is up.
IFF_BROADCAST = 0x2 # Broadcast address valid.
IFF_DEBUG = 0x4 # Turn on debugging.
IFF_LOOPBACK = 0x8 # Is a loopback net.
IFF_POINTOPOINT = 0x10 # Interface is point-to-point link.
IFF_NOTRAILERS = 0x20 # Avoid use of trailers.
IFF_RUNNING = 0x40 # Resources allocated.
IFF_NOARP = 0x80 # No address resolution protocol.
IFF_PROMISC = 0x100 # Receive all packets.
IFF_ALLMULTI = 0x200 # Receive all multicast packets.
IFF_MASTER = 0x400 # Master of a load balancer.
IFF_SLAVE = 0x800 # Slave of a load balancer.
IFF_MULTICAST = 0x1000 # Supports multicast.
IFF_PORTSEL = 0x2000 # Can set media type.
IFF_AUTOMEDIA = 0x4000 # Auto media select active.
def __init__ (self):
# create a socket so we have a handle to query
self.sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def _fcntl (self, func, args):
return fcntl.ioctl(self.sockfd.fileno(), func, args)
def _getaddr (self, ifname, func):
ifreq = struct.pack("32s", ifname)
try:
result = self._fcntl(func, ifreq)
except IOError, msg:
linkcheck.log.warn(linkcheck.LOG,
"error getting addr for interface %r: %s", ifname, msg)
return None
return socket.inet_ntoa(result[20:24])
def getInterfaceList (self):
""" Get all interface names in a list
"""
# get interface list
buf = array.array('c', '\0' * 1024)
ifconf = struct.pack("iP", buf.buffer_info()[1], buf.buffer_info()[0])
result = self._fcntl(self.SIOCGIFCONF, ifconf)
# loop over interface names
iflist = []
size, ptr = struct.unpack("iP", result)
for idx in range(0, size, 32):
ifconf = buf.tostring()[idx:idx+32]
name, dummy = struct.unpack("16s16s", ifconf)
name, dummy = name.split('\0', 1)
iflist.append(name)
return iflist
def getFlags (self, ifname):
""" Get the flags for an interface
"""
ifreq = struct.pack("32s", ifname)
try:
result = self._fcntl(self.SIOCGIFFLAGS, ifreq)
except IOError, msg:
linkcheck.log.warn(linkcheck.LOG_NET,
"error getting flags for interface %r: %s", ifname, msg)
return 0
# extract the interface's flags from the return value
flags, = struct.unpack('H', result[16:18])
# return "UP" bit
return flags
def getAddr (self, ifname):
"""Get the inet addr for an interface"""
return self._getaddr(ifname, self.SIOCGIFADDR)
def getMask (self, ifname):
"""Get the netmask for an interface"""
return self._getaddr(ifname, self.SIOCGIFNETMASK)
def getBroadcast (self, ifname):
"""Get the broadcast addr for an interface"""
return self._getaddr(ifname, self.SIOCGIFBRDADDR)
def isUp (self, ifname):
"""Check whether interface 'ifname' is UP"""
return (self.getFlags(ifname) & self.IFF_UP) != 0

91
linkcheck/dns/inet.py Normal file
View file

@ -0,0 +1,91 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Generic Internet address helper functions."""
import socket
import linkcheck.dns.ipv4
import linkcheck.dns.ipv6
# We assume that AF_INET is always defined.
AF_INET = socket.AF_INET
# AF_INET6 might not be defined in the socket module, but we need it.
# We'll try to use the socket module's value, and if it doesn't work,
# we'll use our own value.
try:
AF_INET6 = socket.AF_INET6
except AttributeError:
AF_INET6 = 9999
def inet_pton(family, text):
"""Convert the textual form of a network address into its binary form.
@param family: the address family
@type family: int
@param text: the textual address
@type text: string
@raises NotImplementedError: the address family specified is not
implemented.
@rtype: string
"""
if family == AF_INET:
return linkcheck.dns.ipv4.inet_aton(text)
elif family == AF_INET6:
return linkcheck.dns.ipv6.inet_aton(text)
else:
raise NotImplementedError
def af_for_address(text):
"""Determine the address family of a textual-form network address.
@param text: the textual address
@type text: string
@raises ValueError: the address family cannot be determined from the input.
@rtype int
"""
try:
junk = linkcheck.dns.ipv4.inet_aton(text)
return AF_INET
except:
try:
junk = linkcheck.dns.ipv6.inet_aton(text)
return AF_INET6
except:
raise ValueError
def inet_ntop(family, address):
"""Convert the binary form of a network address into its textual form.
@param family: the address family
@type family: int
@param address: the binary address
@type address: string
@raises NotImplementedError: the address family specified is not
implemented.
@rtype: string
"""
if family == AF_INET:
return linkcheck.dns.ipv4.inet_ntoa(address)
elif family == AF_INET6:
return linkcheck.dns.ipv6.inet_ntoa(address)
else:
raise NotImplementedError

37
linkcheck/dns/ipv4.py Normal file
View file

@ -0,0 +1,37 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""IPv4 helper functions."""
import socket
import sys
if sys.hexversion < 0x02030000 or sys.platform == 'win32':
#
# Some versions of Python 2.2 have an inet_aton which rejects
# the valid IP address '255.255.255.255'. It appears this
# problem is still present on the Win32 platform even in 2.3.
# We'll work around the problem.
#
def inet_aton(text):
if text == '255.255.255.255':
return '\xff' * 4
else:
return socket.inet_aton(text)
else:
inet_aton = socket.inet_aton
inet_ntoa = socket.inet_ntoa

158
linkcheck/dns/ipv6.py Normal file
View file

@ -0,0 +1,158 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import re
import linkcheck.dns.exception
import linkcheck.dns.ipv4
_leading_zero = re.compile(r'0+([0-9a-f]+)')
def inet_ntoa(address):
"""Convert a network format IPv6 address into text.
@param address: the binary address
@type address: string
@rtype: string
@raises ValueError: the address isn't 16 bytes long
"""
if len(address) != 16:
raise ValueError, "IPv6 addresses are 16 bytes long"
hex = address.encode('hex_codec')
chunks = []
i = 0
l = len(hex)
while i < l:
chunk = hex[i : i + 4]
# strip leading zeros. we do this with an re instead of
# with lstrip() because lstrip() didn't support chars until
# python 2.2.2
m = _leading_zero.match(chunk)
if not m is None:
chunk = m.group(1)
chunks.append(chunk)
i += 4
#
# Compress the longest subsequence of 0-value chunks to ::
#
best_start = 0
best_len = 0
last_was_zero = False
for i in xrange(8):
if chunks[i] != '0':
if last_was_zero:
end = i
current_len = end - start
if current_len > best_len:
best_start = start
best_len = current_len
last_was_zero = False
elif not last_was_zero:
start = i
last_was_zero = True
if last_was_zero:
end = 8
current_len = end - start
if current_len > best_len:
best_start = start
best_len = current_len
if best_len > 0:
if best_start == 0 and \
(best_len == 6 or
best_len == 5 and chunks[5] == 'ffff'):
# We have an embedded IPv4 address
if best_len == 6:
prefix = '::'
else:
prefix = '::ffff:'
hex = prefix + linkcheck.dns.ipv4.inet_ntoa(address[12:])
else:
hex = ':'.join(chunks[:best_start]) + '::' + \
':'.join(chunks[best_start + best_len:])
else:
hex = ':'.join(chunks)
return hex
_v4_ending = re.compile(r'(.*):(\d+)\.(\d+)\.(\d+)\.(\d+)$')
_colon_colon_start = re.compile(r'::.*')
_colon_colon_end = re.compile(r'.*::$')
def inet_aton(text):
"""Convert a text format IPv6 address into network format.
@param text: the textual address
@type text: string
@rtype: string
@raises linkcheck.dns.exception.SyntaxError: the text was not properly formatted
"""
#
# Our aim here is not something fast; we just want something that works.
#
if text == '::':
text = '0::'
#
# Get rid of the icky dot-quad syntax if we have it.
#
m = _v4_ending.match(text)
if not m is None:
text = "%s:%04x:%04x" % (m.group(1),
int(m.group(2)) * 256 + int(m.group(3)),
int(m.group(4)) * 256 + int(m.group(5)))
#
# Try to turn '::<whatever>' into ':<whatever>'; if no match try to
# turn '<whatever>::' into '<whatever>:'
#
m = _colon_colon_start.match(text)
if not m is None:
text = text[1:]
else:
m = _colon_colon_end.match(text)
if not m is None:
text = text[:-1]
#
# Now canonicalize into 8 chunks of 4 hex digits each
#
chunks = text.split(':')
l = len(chunks)
if l > 8:
raise linkcheck.dns.exception.SyntaxError
seen_empty = False
canonical = []
for c in chunks:
if c == '':
if seen_empty:
raise linkcheck.dns.exception.SyntaxError
seen_empty = True
for i in xrange(0, 8 - l + 1):
canonical.append('0000')
else:
lc = len(c)
if lc > 4:
raise linkcheck.dns.exception.SyntaxError
if lc != 4:
c = ('0' * (4 - lc)) + c
canonical.append(c)
if l < 8 and not seen_empty:
raise linkcheck.dns.exception.SyntaxError
text = ''.join(canonical)
#
# Finally we can go to binary.
#
return text.decode('hex_codec')

905
linkcheck/dns/message.py Normal file
View file

@ -0,0 +1,905 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO as StringIO
import random
import struct
import sys
import time
import linkcheck.dns.exception
import linkcheck.dns.flags
import linkcheck.dns.name
import linkcheck.dns.opcode
import linkcheck.dns.rcode
import linkcheck.dns.rdata
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
import linkcheck.dns.rrset
import linkcheck.dns.renderer
import linkcheck.dns.tsig
class ShortHeader(linkcheck.dns.exception.FormError):
"""Raised if the DNS packet passed to from_wire() is too short."""
pass
class TrailingJunk(linkcheck.dns.exception.FormError):
"""Raised if the DNS packet passed to from_wire() has extra junk
at the end of it."""
pass
class UnknownHeaderField(linkcheck.dns.exception.DNSException):
"""Raised if a header field name is not recognized when converting from
text into a message."""
pass
class BadEDNS(linkcheck.dns.exception.FormError):
"""Raised if an OPT record occurs somewhere other than the start of
the additional data section."""
pass
class BadTSIG(linkcheck.dns.exception.FormError):
"""Raised if a TSIG record occurs somewhere other than the end of
the additional data section."""
pass
class UnknownTSIGKey(linkcheck.dns.exception.DNSException):
"""Raised if we got a TSIG but don't know the key."""
pass
class Message(object):
"""A DNS message.
@ivar id: The query id; the default is a randomly chosen id.
@type id: int
@ivar flags: The DNS flags of the message. @see: RFC 1035 for an
explanation of these flags.
@type flags: int
@ivar question: The question section.
@type question: list of linkcheck.dns.rrset.RRset objects
@ivar answer: The answer section.
@type answer: list of linkcheck.dns.rrset.RRset objects
@ivar authority: The authority section.
@type authority: list of linkcheck.dns.rrset.RRset objects
@ivar additional: The additional data section.
@type additional: list of linkcheck.dns.rrset.RRset objects
@ivar edns: The EDNS level to use. The default is -1, no Elinkcheck.dns.
@type edns: int
@ivar ednsflags: The EDNS flags
@type ednsflags: long
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
@ivar keyring: The TSIG keyring to use. The default is None.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: linkcheck.dns.name.Name object
@ivar request_mac: The TSIG MAC of the request message associated with
this message; used when validating TSIG signatures. @see: RFC 2845 for
more information on TSIG fields.
@type request_mac: string
@ivar fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@ivar original_id: TSIG original id; defaults to the message's id
@type original_id: int
@ivar tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@ivar other_data: TSIG other data.
@type other_data: string
@ivar mac: The TSIG MAC for this message.
@type mac: string
@ivar xfr: Is the message being used to contain the results of a DNS
zone transfer? The default is False.
@type xfr: bool
@ivar origin: The origin of the zone in messages which are used for
zone transfers or for DNS dynamic updates. The default is None.
@type origin: linkcheck.dns.name.Name object
@ivar tsig_ctx: The TSIG signature context associated with this
message. The default is None.
@type tsig_ctx: hmac.HMAC object
@ivar had_tsig: Did the message decoded from wire format have a TSIG
signature?
@type had_tsig: bool
@ivar multi: Is this message part of a multi-message sequence? The
default is false. This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type multi: bool
@ivar first: Is this message standalone, or the first of a multi
message sequence? This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type first: bool
"""
def __init__(self, id=None):
if id is None:
self.id = random.randint(0, 65535)
else:
self.id = id
self.flags = 0
self.question = []
self.answer = []
self.authority = []
self.additional = []
self.edns = -1
self.ednsflags = 0
self.payload = 0
self.keyring = None
self.keyname = None
self.request_mac = ''
self.other_data = ''
self.tsig_error = 0
self.fudge = 300
self.original_id = self.id
self.mac = ''
self.xfr = False
self.origin = None
self.tsig_ctx = None
self.had_tsig = False
self.multi = False
self.first = True
def __repr__(self):
return '<DNS message, ID ' + `self.id` + '>'
def __str__(self):
return self.to_text()
def to_text(self, origin=None, relativize=True, **kw):
"""Convert the message to text.
The I{origin}, I{relativize}, and any other keyword
arguments are passed to the rrset to_wire() method.
@rtype: string
"""
s = StringIO.StringIO()
print >> s, 'id %d' % self.id
print >> s, 'opcode %s' % \
linkcheck.dns.opcode.to_text(linkcheck.dns.opcode.from_flags(self.flags))
rc = linkcheck.dns.rcode.from_flags(self.flags, self.ednsflags)
print >> s, 'rcode %s' % linkcheck.dns.rcode.to_text(rc)
print >> s, 'flags %s' % linkcheck.dns.flags.to_text(self.flags)
if self.edns >= 0:
print >> s, 'edns %s' % self.edns
if self.ednsflags != 0:
print >> s, 'eflags %s' % \
linkcheck.dns.flags.edns_to_text(self.ednsflags)
print >> s, 'payload', self.payload
is_update = linkcheck.dns.opcode.is_update(self.flags)
if is_update:
print >> s, ';ZONE'
else:
print >> s, ';QUESTION'
for rrset in self.question:
print >> s, rrset.to_text(origin, relativize, **kw)
if is_update:
print >> s, ';PREREQ'
else:
print >> s, ';ANSWER'
for rrset in self.answer:
print >> s, rrset.to_text(origin, relativize, **kw)
if is_update:
print >> s, ';UPDATE'
else:
print >> s, ';AUTHORITY'
for rrset in self.authority:
print >> s, rrset.to_text(origin, relativize, **kw)
print >> s, ';ADDITIONAL'
for rrset in self.additional:
print >> s, rrset.to_text(origin, relativize, **kw)
#
# We strip off the final \n so the caller can print the result without
# doing weird things to get around eccentricities in Python print
# formatting
#
return s.getvalue()[:-1]
def __eq__(self, other):
"""Two messages are equal if they have the same content in the
header, question, answer, and authority sections.
@rtype: bool"""
if not isinstance(other, Message):
return False
if self.id != other.id:
return False
if self.flags != other.flags:
return False
for n in self.question:
if n not in other.question:
return False
for n in other.question:
if n not in self.question:
return False
for n in self.answer:
if n not in other.answer:
return False
for n in other.answer:
if n not in self.answer:
return False
for n in self.authority:
if n not in other.authority:
return False
for n in other.authority:
if n not in self.authority:
return False
return True
def __ne__(self, other):
"""Are two messages not equal?
@rtype: bool"""
return not self.__eq__(other)
def is_response(self, other):
"""Is other a response to self?
@rtype: bool"""
if other.flags & linkcheck.dns.flags.QR == 0 or \
self.id != other.id or \
linkcheck.dns.opcode.from_flags(self.flags) != \
linkcheck.dns.opcode.from_flags(other.flags):
return False
if linkcheck.dns.rcode.from_flags(other.flags, other.ednsflags) != \
linkcheck.dns.rcode.NOERROR:
return True
if linkcheck.dns.opcode.is_update(self.flags):
return True
for n in self.question:
if n not in other.question:
return False
for n in other.question:
if n not in self.question:
return False
return True
def find_rrset(self, section, name, rdclass, rdtype,
covers=linkcheck.dns.rdatatype.NONE, deleting=None, create=False,
force_unique=False):
"""Find the RRset with the given attributes in the specified section.
@param section: the section of the message to look in, e.g.
self.answer.
@type section: list of linkcheck.dns.rrset.RRset objects
@param name: the name of the RRset
@type name: linkcheck.dns.name.Name object
@param rdclass: the class of the RRset
@type rdclass: int
@param rdtype: the type of the RRset
@type rdtype: int
@param covers: the covers value of the RRset
@type covers: int
@param deleting: the deleting value of the RRset
@type deleting: int
@param create: If True, create the RRset if it is not found.
The created RRset is appended to I{section}.
@type create: bool
@param force_unique: If True and create is also True, create a
new RRset regardless of whether a matching RRset exists already.
@type force_unique: bool
@raises KeyError: the RRset was not found and create was False
@rtype: linkcheck.dns.rrset.RRset object"""
if not force_unique:
for rrset in section:
if rrset.match(name, rdclass, rdtype, covers, deleting):
return rrset
if not create:
raise KeyError
rrset = linkcheck.dns.rrset.RRset(name, rdclass, rdtype, covers, deleting)
section.append(rrset)
return rrset
def get_rrset(self, section, name, rdclass, rdtype,
covers=linkcheck.dns.rdatatype.NONE, deleting=None, create=False,
force_unique=False):
"""Get the RRset with the given attributes in the specified section.
If the RRset is not found, None is returned.
@param section: the section of the message to look in, e.g.
self.answer.
@type section: list of linkcheck.dns.rrset.RRset objects
@param name: the name of the RRset
@type name: linkcheck.dns.name.Name object
@param rdclass: the class of the RRset
@type rdclass: int
@param rdtype: the type of the RRset
@type rdtype: int
@param covers: the covers value of the RRset
@type covers: int
@param deleting: the deleting value of the RRset
@type deleting: int
@param create: If True, create the RRset if it is not found.
The created RRset is appended to I{section}.
@type create: bool
@param force_unique: If True and create is also True, create a
new RRset regardless of whether a matching RRset exists already.
@type force_unique: bool
@rtype: linkcheck.dns.rrset.RRset object or None"""
try:
rrset = self.find_rrset(section, name, rdclass, rdtype, covers,
deleting, create, force_unique)
except KeyError:
rrset = None
return rrset
def to_wire(self, origin=None, max_size=65535, **kw):
"""Return a string containing the message in DNS compressed wire
format.
Additional keyword arguments are passed to the rrset to_wire()
method.
@param origin: The origin to be appended to any relative names.
@type origin: linkcheck.dns.name.Name object
@param max_size: The maximum size of the wire format output.
@type max_size: int
@raises linkcheck.dns.exception.TooBig: max_size was exceeded
@rtype: string
"""
r = linkcheck.dns.renderer.Renderer(self.id, self.flags, max_size, origin)
for rrset in self.question:
r.add_question(rrset.name, rrset.rdtype, rrset.rdclass)
for rrset in self.answer:
r.add_rrset(linkcheck.dns.renderer.ANSWER, rrset, **kw)
for rrset in self.authority:
r.add_rrset(linkcheck.dns.renderer.AUTHORITY, rrset, **kw)
if self.edns >= 0:
r.add_edns(self.edns, self.ednsflags, self.payload)
for rrset in self.additional:
r.add_rrset(linkcheck.dns.renderer.ADDITIONAL, rrset, **kw)
r.write_header()
if not self.keyname is None:
r.add_tsig(self.keyname, self.keyring[self.keyname],
self.fudge, self.original_id, self.tsig_error,
self.other_data, self.request_mac)
self.mac = r.mac
return r.get_wire()
def use_tsig(self, keyring, keyname=None, fudge=300, original_id=None,
tsig_error=0, other_data=''):
"""When sending, a TSIG signature using the specified keyring
and keyname should be added.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@type keyname: linkcheck.dns.name.Name or string
@param fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@param original_id: TSIG original id; defaults to the message's id
@type original_id: int
@param tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@param other_data: TSIG other data.
@type other_data: string
"""
self.keyring = keyring
if keyname is None:
self.keyname = self.keyring.keys()[0]
else:
if isinstance(keyname, str):
keyname = linkcheck.dns.name.from_text(keyname)
self.keyname = keyname
self.fudge = fudge
if original_id is None:
self.original_id = self.id
else:
self.original_id = original_id
self.tsig_error = tsig_error
self.other_data = other_data
def use_edns(self, edns, ednsflags, payload):
"""Configure EDNS behavior.
@param edns: The EDNS level to use. Specifying None or -1 means
'do not use EDNS'.
@type edns: int or None
@param ednsflags: EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle.
@type payload: int
@see: RFC 2671
"""
if edns is None:
edns = -1
self.edns = edns
self.ednsflags = ednsflags
self.payload = payload
def rcode(self):
"""Return the rcode.
@rtype: int
"""
return linkcheck.dns.rcode.from_flags(self.flags, self.ednsflags)
def set_rcode(self, rcode):
"""Set the rcode.
@param rcode: the rcode
@type rcode: int
"""
(value, evalue) = linkcheck.dns.rcode.to_flags(rcode)
self.flags &= 0xFFF0
self.flags |= value
self.ednsflags &= 0xFF000000L
self.ednsflags |= evalue
if self.ednsflags != 0 and self.edns < 0:
self.edns = 0
class _WireReader(object):
"""Wire format reader.
@ivar wire: the wire-format message.
@type wire: string
@ivar message: The message object being built
@type message: linkcheck.dns.message.Message object
@ivar current: When building a message object from wire format, this
variable contains the offset from the beginning of wire of the next octet
to be read.
@type current: int
@ivar updating: Is the message a dynamic update?
@type updating: bool
@ivar zone_rdclass: The class of the zone in messages which are
DNS dynamic updates.
@type zone_rdclass: int
"""
def __init__(self, wire, message, question_only=False):
self.wire = wire
self.message = message
self.current = 0
self.updating = False
self.zone_rdclass = linkcheck.dns.rdataclass.IN
self.question_only = question_only
def _get_question(self, qcount):
"""Read the next I{qcount} records from the wire data and add them to
the question section.
@param qcount: the number of questions in the message
@type qcount: int"""
if self.updating and qcount > 1:
raise linkcheck.dns.exception.FormError
for i in xrange(0, qcount):
(qname, used) = linkcheck.dns.name.from_wire(self.wire, self.current)
if not self.message.origin is None:
qname = qname.relativize(self.message.origin)
self.current = self.current + used
(rdtype, rdclass) = \
struct.unpack('!HH',
self.wire[self.current:self.current + 4])
self.current = self.current + 4
self.message.find_rrset(self.message.question, qname,
rdclass, rdtype, create=True,
force_unique=True)
if self.updating:
self.zone_rdclass = rdclass
def _get_section(self, section, count):
"""Read the next I{count} records from the wire data and add them to
the specified section.
@param section: the section of the message to which to add records
@type section: list of linkcheck.dns.rrset.RRset objects
@param count: the number of records to read
@type count: int"""
if self.updating:
force_unique = True
else:
force_unique = False
seen_opt = False
for i in xrange(0, count):
rr_start = self.current
(name, used) = linkcheck.dns.name.from_wire(self.wire, self.current)
if not self.message.origin is None:
name = name.relativize(self.message.origin)
self.current = self.current + used
(rdtype, rdclass, ttl, rdlen) = \
struct.unpack('!HHIH',
self.wire[self.current:self.current + 10])
self.current = self.current + 10
if rdtype == linkcheck.dns.rdatatype.OPT:
if not section is self.message.additional or seen_opt:
raise BadEDNS
self.message.payload = rdclass
self.message.ednsflags = ttl
self.message.edns = (ttl & 0xff0000) >> 16
seen_opt = True
elif rdtype == linkcheck.dns.rdatatype.TSIG:
if not (section is self.message.additional and
i == (count - 1)):
raise BadTSIG
if self.message.keyring is None:
raise UnknownTSIGKey, 'got signed message without keyring'
secret = self.message.keyring.get(name)
if secret is None:
raise UnknownTSIGKey, "key '%s' unknown" % name
self.message.tsig_ctx = \
linkcheck.dns.tsig.validate(self.wire,
name,
secret,
int(time.time()),
self.message.request_mac,
rr_start,
self.current,
rdlen,
self.message.tsig_ctx,
self.message.multi,
self.message.first)
self.message.had_tsig = True
else:
if ttl < 0:
ttl = 0
if self.updating and \
(rdclass == linkcheck.dns.rdataclass.ANY or
rdclass == linkcheck.dns.rdataclass.NONE):
deleting = rdclass
rdclass = self.zone_rdclass
else:
deleting = None
if deleting == linkcheck.dns.rdataclass.ANY:
covers = linkcheck.dns.rdatatype.NONE
rd = None
else:
rd = linkcheck.dns.rdata.from_wire(rdclass, rdtype, self.wire,
self.current, rdlen,
self.message.origin)
covers = rd.covers()
if self.message.xfr and rdtype == linkcheck.dns.rdatatype.SOA:
force_unique = True
rrset = self.message.find_rrset(section, name,
rdclass, rdtype, covers,
deleting, True, force_unique)
if not rd is None:
rrset.add(rd, ttl)
self.current = self.current + rdlen
def read(self):
"""Read a wire format DNS message and build a linkcheck.dns.message.Message
object."""
l = len(self.wire)
if l < 12:
raise ShortHeader
(self.message.id, self.message.flags, qcount, ancount,
aucount, adcount) = struct.unpack('!HHHHHH', self.wire[:12])
self.current = 12
if linkcheck.dns.opcode.is_update(self.message.flags):
self.updating = True
self._get_question(qcount)
if self.question_only:
return
self._get_section(self.message.answer, ancount)
self._get_section(self.message.authority, aucount)
self._get_section(self.message.additional, adcount)
if self.current != l:
raise TrailingJunk
if self.message.multi and self.message.tsig_ctx and \
not self.message.had_tsig:
self.message.tsig_ctx.update(self.wire)
def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None,
tsig_ctx = None, multi = False, first = True,
question_only = False):
"""Convert a DNS wire format message into a message
object.
@param keyring: The keyring to use if the message is signed.
@type keyring: dict
@param request_mac: If the message is a response to a TSIG-signed request,
I{request_mac} should be set to the MAC of that request.
@type request_mac: string
@param xfr: Is this message part of a zone transfer?
@type xfr: bool
@param origin: If the message is part of a zone transfer, I{origin}
should be the origin name of the zone.
@type origin: linkcheck.dns.name.Name object
@param tsig_ctx: The ongoing TSIG context, used when validating zone
transfers.
@type tsig_ctx: hmac.HMAC object
@param multi: Is this message part of a multiple message sequence?
@type multi: bool
@param first: Is this message standalone, or the first of a multi
message sequence?
@type first: bool
@param question_only: Read only up to the end of the question section?
@type question_only: bool
@raises ShortHeader: The message is less than 12 octets long.
@raises TrailingJunk: There were octets in the message past the end
of the proper DNS message.
@raises BadEDNS: An OPT record was in the wrong section, or occurred more
than once.
@raises BadTSIG: A TSIG record was not the last record of the additional
data section.
@rtype: linkcheck.dns.message.Message object"""
m = Message(id=0)
m.keyring = keyring
m.request_mac = request_mac
m.xfr = xfr
m.origin = origin
m.tsig_ctx = tsig_ctx
m.multi = multi
m.first = first
reader = _WireReader(wire, m, question_only)
reader.read()
return m
class _TextReader(object):
"""Text format reader.
@ivar tok: the tokenizer
@type tok: linkcheck.dns.tokenizer.Tokenizer object
@ivar message: The message object being built
@type message: linkcheck.dns.message.Message object
@ivar updating: Is the message a dynamic update?
@type updating: bool
@ivar zone_rdclass: The class of the zone in messages which are
DNS dynamic updates.
@type zone_rdclass: int
@ivar last_name: The most recently read name when building a message object
from text format.
@type last_name: linkcheck.dns.name.Name object
"""
def __init__(self, text, message):
self.message = message
self.tok = linkcheck.dns.tokenizer.Tokenizer(text)
self.last_name = None
self.zone_rdclass = linkcheck.dns.rdataclass.IN
self.updating = False
def _header_line(self, section):
"""Process one line from the text format header section."""
(ttype, what) = self.tok.get()
if what == 'id':
self.message.id = self.tok.get_int()
elif what == 'flags':
while True:
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
self.tok.unget(token)
break
self.message.flags = self.message.flags | \
linkcheck.dns.flags.from_text(token[1])
if linkcheck.dns.opcode.is_update(self.message.flags):
self.updating = True
elif what == 'edns':
self.message.edns = self.tok.get_int()
self.message.ednsflags = self.message.ednsflags | \
(self.message.edns << 16)
elif what == 'eflags':
if self.message.edns < 0:
self.message.edns = 0
while True:
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
self.tok.unget(token)
break
self.message.ednsflags = self.message.ednsflags | \
linkcheck.dns.flags.edns_from_text(token[1])
elif what == 'payload':
self.message.payload = self.tok.get_int()
if self.message.edns < 0:
self.message.edns = 0
elif what == 'opcode':
text = self.tok.get_string()
self.message.flags = self.message.flags | \
linkcheck.dns.opcode.to_flags(linkcheck.dns.opcode.from_text(text))
elif what == 'rcode':
text = self.tok.get_string()
self.message.set_rcode(linkcheck.dns.rcode.from_text(text))
else:
raise UnknownHeaderField
self.tok.get_eol()
def _question_line(self, section):
"""Process one line from the text format question section."""
token = self.tok.get(want_leading = True)
if token[0] != linkcheck.dns.tokenizer.WHITESPACE:
self.last_name = linkcheck.dns.name.from_text(token[1], None)
name = self.last_name
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
# Class
try:
rdclass = linkcheck.dns.rdataclass.from_text(token[1])
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
except linkcheck.dns.exception.SyntaxError:
raise linkcheck.dns.exception.SyntaxError
except:
rdclass = linkcheck.dns.rdataclass.IN
# Type
rdtype = linkcheck.dns.rdatatype.from_text(token[1])
self.message.find_rrset(self.message.question, name,
rdclass, rdtype, create=True,
force_unique=True)
if self.updating:
self.zone_rdclass = rdclass
self.tok.get_eol()
def _rr_line(self, section):
"""Process one line from the text format answer, authority, or
additional data sections.
"""
deleting = None
# Name
token = self.tok.get(want_leading = True)
if token[0] != linkcheck.dns.tokenizer.WHITESPACE:
self.last_name = linkcheck.dns.name.from_text(token[1], None)
name = self.last_name
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
# TTL
try:
ttl = int(token[1], 0)
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
except linkcheck.dns.exception.SyntaxError:
raise linkcheck.dns.exception.SyntaxError
except:
ttl = 0
# Class
try:
rdclass = linkcheck.dns.rdataclass.from_text(token[1])
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
if rdclass == linkcheck.dns.rdataclass.ANY or rdclass == linkcheck.dns.rdataclass.NONE:
deleting = rdclass
rdclass = self.zone_rdclass
except linkcheck.dns.exception.SyntaxError:
raise linkcheck.dns.exception.SyntaxError
except:
rdclass = linkcheck.dns.rdataclass.IN
# Type
rdtype = linkcheck.dns.rdatatype.from_text(token[1])
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.EOL and token[0] != linkcheck.dns.tokenizer.EOF:
self.tok.unget(token)
rd = linkcheck.dns.rdata.from_text(rdclass, rdtype, self.tok, None)
covers = rd.covers()
else:
rd = None
covers = linkcheck.dns.rdatatype.NONE
rrset = self.message.find_rrset(section, name,
rdclass, rdtype, covers,
deleting, True, self.updating)
if not rd is None:
rrset.add(rd, ttl)
def read(self):
"""Read a text format DNS message and build a linkcheck.dns.message.Message
object."""
line_method = self._header_line
section = None
while 1:
token = self.tok.get(True, True)
if token[0] == linkcheck.dns.tokenizer.EOL or token[0] == linkcheck.dns.tokenizer.EOF:
break
if token[0] == linkcheck.dns.tokenizer.COMMENT:
u = token[1].upper()
if u == 'HEADER':
line_method = self._header_line
elif u == 'QUESTION' or u == 'ZONE':
line_method = self._question_line
section = self.message.question
elif u == 'ANSWER' or u == 'PREREQ':
line_method = self._rr_line
section = self.message.answer
elif u == 'AUTHORITY' or u == 'UPDATE':
line_method = self._rr_line
section = self.message.authority
elif u == 'ADDITIONAL':
line_method = self._rr_line
section = self.message.additional
self.tok.get_eol()
continue
self.tok.unget(token)
line_method(section)
def from_text(text):
"""Convert the text format message into a message object.
@param text: The text format message.
@type text: string
@raises UnknownHeaderField:
@raises linkcheck.dns.exception.SyntaxError:
@rtype: linkcheck.dns.message.Message object"""
# 'text' can also be a file, but we don't publish that fact
# since it's an implementation detail. The official file
# interface is from_file().
m = Message()
reader = _TextReader(text, m)
reader.read()
return m
def from_file(f):
"""Read the next text format message from the specified file.
@param f: file or string. If I{f} is a string, it is treated
as the name of a file to open.
@raises UnknownHeaderField:
@raises linkcheck.dns.exception.SyntaxError:
@rtype: linkcheck.dns.message.Message object"""
if sys.hexversion >= 0x02030000:
# allow Unicode filenames; turn on universal newline support
str_type = basestring
opts = 'rU'
else:
str_type = str
opts = 'r'
if isinstance(f, str_type):
f = file(f, opts)
want_close = True
else:
want_close = False
try:
m = from_text(f)
finally:
if want_close:
f.close()
return m
def make_query(qname, rdtype, rdclass = linkcheck.dns.rdataclass.IN):
"""Make a query message.
The query name, type, and class may all be specified either
as objects of the appropriate type, or as strings.
The query will have a randomly choosen query id, and its DNS flags
will be set to linkcheck.dns.flags.RD.
@param qname: The query name.
@type qname: linkcheck.dns.name.Name object or string
@param rdtype: The desired rdata type.
@type rdtype: int
@param rdclass: The desired rdata class; the default is class IN.
@type rdclass: int
@rtype: linkcheck.dns.message.Message object"""
if isinstance(qname, str):
qname = linkcheck.dns.name.from_text(qname)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(rdclass, str):
rdclass = linkcheck.dns.rdataclass.from_text(rdclass)
m = Message()
m.flags |= linkcheck.dns.flags.RD
m.find_rrset(m.question, qname, rdclass, rdtype, create=True,
force_unique=True)
return m

586
linkcheck/dns/name.py Normal file
View file

@ -0,0 +1,586 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Names.
@var root: The DNS root name.
@type root: linkcheck.dns.name.Name object
@var empty: The empty DNS name.
@type empty: linkcheck.dns.name.Name object
"""
import string
import struct
import sys
import linkcheck.dns.exception
NAMERELN_NONE = 0
NAMERELN_SUPERDOMAIN = 1
NAMERELN_SUBDOMAIN = 2
NAMERELN_EQUAL = 3
NAMERELN_COMMONANCESTOR = 4
class EmptyLabel(linkcheck.dns.exception.SyntaxError):
"""Raised if a label is empty."""
pass
class BadEscape(linkcheck.dns.exception.SyntaxError):
"""Raised if an escaped code in a text format name is invalid."""
pass
class BadPointer(linkcheck.dns.exception.FormError):
"""Raised if a compression pointer points forward instead of backward."""
pass
class BadLabelType(linkcheck.dns.exception.FormError):
"""Raised if the label type of a wire format name is unknown."""
pass
class NeedAbsoluteNameOrOrigin(linkcheck.dns.exception.DNSException):
"""Raised if an attempt is made to convert a non-absolute name to
wire when there is also a non-absolute (or missing) origin."""
pass
class NameTooLong(linkcheck.dns.exception.FormError):
"""Raised if a name is > 255 octets long."""
pass
class LabelTooLong(linkcheck.dns.exception.SyntaxError):
"""Raised if a label is > 63 octets long."""
pass
class AbsoluteConcatenation(linkcheck.dns.exception.DNSException):
"""Raised if an attempt is made to append anything other than the
empty name to an absolute name."""
pass
_escaped = {
'"' : True,
'(' : True,
')' : True,
'.' : True,
';' : True,
'\\' : True,
'@' : True,
'$' : True
}
def _escapify(label):
"""Escape the characters in label which need it.
@returns: the escaped string
@rtype: string"""
text = ''
for c in label:
if c in _escaped:
text += '\\' + c
elif ord(c) > 0x20 and ord(c) < 0x7F:
text += c
else:
text += '\\%03d' % ord(c)
return text
def _validate_labels(labels):
"""Check for empty labels in the middle of a label sequence,
labels that are too long, and for too many labels.
@raises NameTooLong: the name as a whole is too long
@raises LabelTooLong: an individual label is too long
@raises EmptyLabel: a label is empty (i.e. the root label) and appears
in a position other than the end of the label sequence"""
l = len(labels)
total = 0
i = -1
j = 0
for label in labels:
ll = len(label)
total += ll + 1
if ll > 63:
raise LabelTooLong
if i < 0 and label == '':
i = j
j += 1
if total > 255:
raise NameTooLong
if i >= 0 and i != l - 1:
raise EmptyLabel
class Name(object):
"""A DNS name.
The linkcheck.dns.name.Name class represents a DNS name as a tuple of labels.
Instances of the class are immutable.
@ivar labels: The tuple of labels in the name. Each label is a string of
up to 63 octets."""
__slots__ = ['labels']
def __init__(self, labels):
"""Initialize a domain name from a list of labels.
@param labels: the labels
@type labels: any iterable whose values are strings
"""
super(Name, self).__setattr__('labels', tuple(labels))
_validate_labels(self.labels)
def __setattr__(self, name, value):
raise TypeError, "object doesn't support attribute assignment"
def is_absolute(self):
"""Is the most significant label of this name the root label?
@rtype: bool
"""
return len(self.labels) > 0 and self.labels[-1] == ''
def is_wild(self):
"""Is this name wild? (I.e. Is the least significant label '*'?)
@rtype: bool
"""
return len(self.labels) > 0 and self.labels[0] == '*'
def __hash__(self):
"""Return a case-insensitive hash of the name.
@rtype: int
"""
h = 0L
for label in self.labels:
for c in label:
h += ( h << 3 ) + ord(c.lower())
return int(h % sys.maxint)
def fullcompare(self, other):
"""Compare two names, returning a 3-tuple (relation, order, nlabels).
I{relation} describes the relation ship beween the names,
and is one of: linkcheck.dns.name.NAMERELN_NONE,
linkcheck.dns.name.NAMERELN_SUPERDOMAIN, linkcheck.dns.name.NAMERELN_SUBDOMAIN,
linkcheck.dns.name.NAMERELN_EQUAL, or linkcheck.dns.name.NAMERELN_COMMONANCESTOR
I{order} is < 0 if self < other, > 0 if self > other, and ==
0 if self == other. A relative name is always less than an
absolute name. If both names have the same relativity, then
the DNSSEC order relation is used to order them.
I{nlabels} is the number of significant labels that the two names
have in common.
"""
sabs = self.is_absolute()
oabs = other.is_absolute()
if sabs != oabs:
if sabs:
return (NAMERELN_NONE, 1, 0)
else:
return (NAMERELN_NONE, -1, 0)
l1 = len(self.labels)
l2 = len(other.labels)
ldiff = l1 - l2
if ldiff < 0:
l = l1
else:
l = l2
order = 0
nlabels = 0
namereln = NAMERELN_NONE
while l > 0:
l -= 1
l1 -= 1
l2 -= 1
label1 = self.labels[l1].lower()
label2 = other.labels[l2].lower()
if label1 < label2:
order = -1
if nlabels > 0:
namereln = NAMERELN_COMMONANCESTOR
return (namereln, order, nlabels)
elif label1 > label2:
order = 1
if nlabels > 0:
namereln = NAMERELN_COMMONANCESTOR
return (namereln, order, nlabels)
nlabels += 1
order = ldiff
if ldiff < 0:
namereln = NAMERELN_SUPERDOMAIN
elif ldiff > 0:
namereln = NAMERELN_SUBDOMAIN
else:
namereln = NAMERELN_EQUAL
return (namereln, order, nlabels)
def is_subdomain(self, other):
"""Is self a subdomain of other?
The notion of subdomain includes equality.
@rtype: bool
"""
(nr, o, nl) = self.fullcompare(other)
if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
return True
return False
def is_superdomain(self, other):
"""Is self a superdomain of other?
The notion of subdomain includes equality.
@rtype: bool
"""
(nr, o, nl) = self.fullcompare(other)
if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
return True
return False
def canonicalize(self):
"""Return a name which is equal to the current name, but is in
DNSSEC canonical form.
@rtype: linkcheck.dns.name.Name object
"""
return Name([x.lower() for x in self.labels])
def __eq__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] == 0
else:
return False
def __ne__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] != 0
else:
return True
def __lt__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] < 0
else:
return NotImplemented
def __le__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] <= 0
else:
return NotImplemented
def __ge__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] >= 0
else:
return NotImplemented
def __gt__(self, other):
if isinstance(other, Name):
return self.fullcompare(other)[1] > 0
else:
return NotImplemented
def __repr__(self):
return '<DNS name ' + self.__str__() + '>'
def __str__(self):
return self.to_text(False)
def to_text(self, omit_final_dot = False):
"""Convert name to text format.
@param omit_final_dot: If True, don't emit the final dot (denoting the
root label) for absolute names. The default is False.
@rtype: string
"""
if len(self.labels) == 0:
return '@'
if len(self.labels) == 1 and self.labels[0] == '':
return '.'
if omit_final_dot and self.is_absolute():
l = self.labels[:-1]
else:
l = self.labels
s = string.join(map(_escapify, l), '.')
return s
def to_digestable(self, origin=None):
"""Convert name to a format suitable for digesting in hashes.
The name is canonicalized and converted to uncompressed wire format.
@param origin: If the name is relative and origin is not None, then
origin will be appended to it.
@type origin: linkcheck.dns.name.Name object
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied;
if it is missing, then this exception is raised
@rtype: string
"""
if not self.is_absolute():
if origin is None or not origin.is_absolute():
raise NeedAbsoluteNameOrOrigin
labels = list(self.labels)
labels.extend(list(origin.labels))
else:
labels = self.labels
dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels]
return ''.join(dlabels)
def to_wire(self, file, compress = None, origin = None):
"""Convert name to wire format, possibly compressing it.
@param file: the file where the compressed name is emitted (typically
a cStringIO file)
@type file: file
@param compress: The compression table. If None (the default) names
will not be compressed.
@type compress: dict
@param origin: If the name is relative and origin is not None, then
origin will be appended to it.
@type origin: linkcheck.dns.name.Name object
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied;
if it is missing, then this exception is raised
"""
if not self.is_absolute():
if origin is None or not origin.is_absolute():
raise NeedAbsoluteNameOrOrigin
labels = list(self.labels)
labels.extend(list(origin.labels))
else:
labels = self.labels
i = 0
for label in labels:
n = Name(labels[i:])
i += 1
if not compress is None:
pos = compress.get(n)
else:
pos = None
if not pos is None:
value = 0xc000 + pos
s = struct.pack('!H', value)
file.write(s)
return
else:
if not compress is None and len(n) > 1:
pos = file.tell()
if pos < 0xc000:
compress[n] = pos
l = len(label)
file.write(chr(l))
if l > 0:
file.write(label)
def __len__(self):
"""The length of the name (in labels).
@rtype: int
"""
return len(self.labels)
def __getitem__(self, index):
return self.labels[index]
def __getslice__(self, start, stop):
return self.labels[start:stop]
def __add__(self, other):
return self.concatenate(other)
def __sub__(self, other):
return self.relativize(other)
def split(self, depth):
"""Split a name into a prefix and suffix at depth.
@param depth: the number of labels in the suffix
@type depth: int
@raises ValueError: the depth was not >= 0 and <= the length of the
name.
@returns: the tuple (prefix, suffix)
@rtype: tuple
"""
l = len(self.labels)
if depth == 0:
return (self, linkcheck.dns.name.empty)
elif depth == l:
return (linkcheck.dns.name.empty, self)
elif depth < 0 or depth > l:
raise ValueError, \
'depth must be >= 0 and <= the length of the name'
return (Name(self[: -depth]), Name(self[-depth :]))
def concatenate(self, other):
"""Return a new name which is the concatenation of self and other.
@rtype: linkcheck.dns.name.Name object
@raises AbsoluteConcatenation: self is absolute and other is
not the empty name
"""
if self.is_absolute() and len(other) > 0:
raise AbsoluteConcatenation
labels = list(self.labels)
labels.extend(list(other.labels))
return Name(labels)
def relativize(self, origin):
"""If self is a subdomain of origin, return a new name which is self
relative to origin. Otherwise return self.
@rtype: linkcheck.dns.name.Name object
"""
if not origin is None and self.is_subdomain(origin):
return Name(self[: -len(origin)])
else:
return self
def derelativize(self, origin):
"""If self is a relative name, return a new name which is the
concatenation of self and origin. Otherwise return self.
@rtype: linkcheck.dns.name.Name object
"""
if not self.is_absolute():
return self.concatenate(origin)
else:
return self
def choose_relativity(self, origin=None, relativize=True):
"""Return a name with the relativity desired by the caller. If
origin is None, then self is returned. Otherwise, if
relativize is true the name is relativized, and if relativize is
false the name is derelativized.
@rtype: linkcheck.dns.name.Name object
"""
if origin:
if relativize:
return self.relativize(origin)
else:
return self.derelativize(origin)
else:
return self
root = Name([''])
empty = Name([])
def from_text(text, origin = root):
"""Convert text into a Name object.
@rtype: linkcheck.dns.name.Name object
"""
if not isinstance(text, str):
raise ValueError, "input to from_text() must be a byte string"
if not (origin is None or isinstance(origin, Name)):
raise ValueError, "origin must be a Name or None"
labels = []
label = ''
escaping = False
if text == '@':
text = ''
if text:
if text == '.':
return Name([''])
for c in text:
if escaping:
if edigits == 0:
if c.isdigit():
total = int(c)
edigits += 1
else:
label += c
escaping = False
else:
if not c.isdigit():
raise BadEscape
total *= 10
total += int(c)
edigits += 1
if edigits == 3:
escaping = False
label += chr(total)
elif c == '.':
if len(label) == 0:
raise EmptyLabel
labels.append(label)
label = ''
elif c == '\\':
escaping = True
edigits = 0
total = 0
else:
label += c
if escaping:
raise BadEscape
if len(label) > 0:
labels.append(label)
else:
labels.append('')
if (len(labels) == 0 or labels[-1] != '') and not origin is None:
labels.extend(list(origin.labels))
return Name(labels)
def from_wire(message, current):
"""Convert possibly compressed wire format into a Name.
@param message: the entire DNS message
@type message: string
@param current: the offset of the beginning of the name from the start
of the message
@type current: int
@raises linkcheck.dns.name.BadPointer: a compression pointer did not point backwards
in the message
@raises linkcheck.dns.name.BadLabelType: an invalid label type was encountered.
@returns: a tuple consisting of the name that was read and the number
of bytes of the wire format message which were consumed reading it
@rtype: (linkcheck.dns.name.Name object, int) tuple
"""
if not isinstance(message, str):
raise ValueError, "input to from_wire() must be a byte string"
labels = []
biggest_pointer = current
hops = 0
count = ord(message[current])
current += 1
cused = 1
while count != 0:
if count < 64:
labels.append(message[current : current + count])
current += count
if hops == 0:
cused += count
elif count >= 192:
current = (count & 0x3f) * 256 + ord(message[current])
if hops == 0:
cused += 1
if current >= biggest_pointer:
raise BadPointer
biggest_pointer = current
hops += 1
else:
raise BadLabelType
count = ord(message[current])
current += 1
if hops == 0:
cused += 1
labels.append('')
return (Name(labels), cused)

60
linkcheck/dns/namedict.py Normal file
View file

@ -0,0 +1,60 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS name dictionary"""
import linkcheck.dns.name
class NameDict(dict):
"""A dictionary whose keys are linkcheck.dns.name.Name objects.
@ivar max_depth: the maximum depth of the keys that have ever been
added to the dictionary.
@type max_depth: int
"""
def __init__(self, *args, **kwargs):
super(NameDict, self).__init__(*args, **kwargs)
self.max_depth = 0
def __setitem__(self, key, value):
if not isinstance(key, linkcheck.dns.name.Name):
raise ValueError, 'NameDict key must be a name'
depth = len(key)
if depth > self.max_depth:
self.max_depth = depth
super(NameDict, self).__setitem__(key, value)
def get_deepest_match(self, name):
"""Find the deepest match to I{name} in the dictionary.
The deepest match is the longest name in the dictionary which is
a superdomain of I{name}.
@param name: the name
@type name: linkcheck.dns.name.Name object
@rtype: (key, value) tuple
"""
depth = len(name)
if depth > self.max_depth:
depth = self.max_depth
for i in xrange(-depth, 0):
n = linkcheck.dns.name.Name(name[i:])
if self.has_key(n):
return (n, self[n])
v = self[linkcheck.dns.name.empty]
return (linkcheck.dns.name.empty, v)

173
linkcheck/dns/node.py Normal file
View file

@ -0,0 +1,173 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS nodes. A node is a set of rdatasets."""
import StringIO
import linkcheck.dns.rdataset
import linkcheck.dns.rdatatype
import linkcheck.dns.renderer
class Node(object):
"""A DNS node.
A node is a set of rdatasets
@ivar rdatasets: the node's rdatasets
@type rdatasets: list of linkcheck.dns.rdataset.Rdataset objects"""
__slots__ = ['rdatasets']
def __init__(self):
"""Initialize a DNS node.
"""
self.rdatasets = [];
def to_text(self, name, **kw):
"""Convert a node to text format.
Each rdataset at the node is printed. Any keyword arguments
to this method are passed on to the rdataset's to_text() method.
@param name: the owner name of the rdatasets
@type name: linkcheck.dns.name.Name object
@rtype: string
"""
s = StringIO.StringIO()
for rds in self.rdatasets:
print >> s, rds.to_text(name, **kw)
return s.getvalue()[:-1]
def __repr__(self):
return '<DNS node ' + str(id(self)) + '>'
def __eq__(self, other):
"""Two nodes are equal if they have the same rdatasets.
@rtype: bool
"""
#
# This is inefficient. Good thing we don't need to do it much.
#
for rd in self.rdatasets:
if rd not in other.rdatasets:
return False
for rd in other.rdatasets:
if rd not in self.rdatasets:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __len__(self):
return len(self.rdatasets)
def __iter__(self):
return iter(self.rdatasets)
def find_rdataset(self, rdclass, rdtype, covers=linkcheck.dns.rdatatype.NONE,
create=False):
"""Find an rdataset matching the specified properties in the
current node.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type. Usually this value is
linkcheck.dns.rdatatype.NONE, but if the rdtype is linkcheck.dns.rdatatype.SIG or
linkcheck.dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
@type covers: int
@param create: If True, create the rdataset if it is not found.
@type create: bool
@raises KeyError: An rdataset of the desired type and class does
not exist and I{create} is not True.
@rtype: linkcheck.dns.rdataset.Rdataset object
"""
for rds in self.rdatasets:
if rds.match(rdclass, rdtype, covers):
return rds
if not create:
raise KeyError
rds = linkcheck.dns.rdataset.Rdataset(rdclass, rdtype)
self.rdatasets.append(rds)
return rds
def get_rdataset(self, rdclass, rdtype, covers=linkcheck.dns.rdatatype.NONE,
create=False):
"""Get an rdataset matching the specified properties in the
current node.
None is returned if an rdataset of the specified type and
class does not exist and I{create} is not True.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type.
@type covers: int
@param create: If True, create the rdataset if it is not found.
@type create: bool
@rtype: linkcheck.dns.rdataset.Rdataset object or None
"""
try:
rds = self.find_rdataset(rdclass, rdtype, covers, create)
except KeyError:
rds = None
return rds
def delete_rdataset(self, rdclass, rdtype, covers=linkcheck.dns.rdatatype.NONE):
"""Delete the rdataset matching the specified properties in the
current node.
If a matching rdataset does not exist, it is not an error.
@param rdclass: The class of the rdataset
@type rdclass: int
@param rdtype: The type of the rdataset
@type rdtype: int
@param covers: The covered type.
@type covers: int
"""
rds = self.get_rdataset(rdclass, rdtype, covers)
if not rds is None:
self.rdatasets.remove(rds)
def replace_rdataset(self, replacement):
"""Replace an rdataset.
It is not an error if there is no rdataset matching I{replacement}.
Ownership of the I{replacement} object is transferred to the node;
in other words, this method does not store a copy of I{replacement}
at the node, it stores I{replacement} itself.
"""
self.delete_rdataset(replacement.rdclass, replacement.rdtype,
replacement.covers)
self.rdatasets.append(replacement)

105
linkcheck/dns/opcode.py Normal file
View file

@ -0,0 +1,105 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Opcodes."""
import linkcheck.dns.exception
QUERY = 0
IQUERY = 1
STATUS = 2
NOTIFY = 4
UPDATE = 5
_by_text = {
'QUERY' : QUERY,
'IQUERY' : IQUERY,
'STATUS' : STATUS,
'NOTIFY' : NOTIFY,
'UPDATE' : UPDATE
}
# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse.
_by_value = dict([(y, x) for x, y in _by_text.iteritems()])
class UnknownOpcode(linkcheck.dns.exception.DNSException):
"""Raised if an opcode is unknown."""
pass
def from_text(text):
"""Convert text into an opcode.
@param text: the textual opcode
@type text: string
@raises UnknownOpcode: the opcode is unknown
@rtype: int
"""
if text.isdigit():
value = int(text)
if value >= 0 and value <= 15:
return value
value = _by_text.get(text.upper())
if value is None:
raise UnknownOpcode
return value
def from_flags(flags):
"""Extract an opcode from DNS message flags.
@param flags: int
@rtype: int
"""
return (flags & 0x7800) >> 11
def to_flags(value):
"""Convert an opcode to a value suitable for ORing into DNS message
flags.
@rtype: int
"""
return (value << 11) & 0x7800
def to_text(value):
"""Convert an opcode to text.
@param value: the opcdoe
@type value: int
@raises UnknownOpcode: the opcode is unknown
@rtype: string
"""
text = _by_value.get(value)
if text is None:
text = str(value)
return text
def is_update(flags):
"""True if the opcode in flags is UPDATE.
@param flags: DNS flags
@type flags: int
@rtype: bool
"""
if (from_flags(flags) == UPDATE):
return True
return False

304
linkcheck/dns/query.py Normal file
View file

@ -0,0 +1,304 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Talk to a DNS server."""
from __future__ import generators
import errno
import select
import socket
import struct
import sys
import time
import linkcheck.dns.exception
import linkcheck.dns.inet
import linkcheck.dns.name
import linkcheck.dns.message
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
class UnexpectedSource(linkcheck.dns.exception.DNSException):
"""Raised if a query response comes from an unexpected address or port."""
pass
class BadResponse(linkcheck.dns.exception.FormError):
"""Raised if a query response does not respond to the question asked."""
pass
def _compute_expiration(timeout):
if timeout is None:
return None
else:
return time.time() + timeout
def _wait_for(ir, iw, ix, expiration):
if expiration is None:
timeout = None
else:
timeout = expiration - time.time()
if timeout <= 0.0:
raise linkcheck.dns.exception.Timeout
if timeout is None:
(r, w, x) = select.select(ir, iw, ix)
else:
(r, w, x) = select.select(ir, iw, ix, timeout)
if len(r) == 0 and len(w) == 0 and len(x) == 0:
raise linkcheck.dns.exception.Timeout
def _wait_for_readable(s, expiration):
_wait_for([s], [], [s], expiration)
def _wait_for_writable(s, expiration):
_wait_for([], [s], [s], expiration)
def udp(q, where, timeout=None, port=53, af=None):
"""Return the response obtained after sending a query via UDP.
@param q: the query
@type q: linkcheck.dns.message.Message
@param where: where to send the message
@type where: string
@param timeout: The number of seconds to wait before the query times out.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@rtype: linkcheck.dns.message.Message object
"""
wire = q.to_wire()
if af is None:
try:
af = linkcheck.dns.inet.af_for_address(where)
except:
af = linkcheck.dns.inet.AF_INET
if af == linkcheck.dns.inet.AF_INET:
destination = (where, port)
elif af == linkcheck.dns.inet.AF_INET6:
destination = (where, port, 0, 0)
s = socket.socket(af, socket.SOCK_DGRAM, 0)
try:
expiration = _compute_expiration(timeout)
s.setblocking(0)
_wait_for_writable(s, expiration)
s.sendto(wire, destination)
_wait_for_readable(s, expiration)
(wire, from_address) = s.recvfrom(65535)
finally:
s.close()
if from_address != destination:
raise UnexpectedSource
r = linkcheck.dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac)
if not q.is_response(r):
raise BadResponse
return r
def _net_read(sock, count, expiration):
"""Read the specified number of bytes from sock. Keep trying until we
either get the desired amount, or we hit EOF.
A Timeout exception will be raised if the operation is not completed
by the expiration time.
"""
s = ''
while count > 0:
_wait_for_readable(sock, expiration)
n = sock.recv(count)
if n == '':
raise EOFError
count -= len(n)
s += n
return s
def _net_write(sock, data, expiration):
"""Write the specified data to the socket.
A Timeout exception will be raised if the operation is not completed
by the expiration time.
"""
current = 0
l = len(data)
while current < l:
_wait_for_writable(sock, expiration)
current += sock.send(data[current:])
def _connect(s, address):
try:
s.connect(address)
except socket.error:
(ty, v) = sys.exc_info()[:2]
if v[0] != errno.EINPROGRESS and v[0] != errno.EWOULDBLOCK:
raise ty, v
def tcp(q, where, timeout=None, port=53, af=None):
"""Return the response obtained after sending a query via TCP.
@param q: the query
@type q: linkcheck.dns.message.Message object
@param where: where to send the message
@type where: string
@param timeout: The number of seconds to wait before the query times out.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@rtype: linkcheck.dns.message.Message object"""
wire = q.to_wire()
if af is None:
try:
af = linkcheck.dns.inet.af_for_address(where)
except:
af = linkcheck.dns.inet.AF_INET
if af == linkcheck.dns.inet.AF_INET:
destination = (where, port)
elif af == linkcheck.dns.inet.AF_INET6:
destination = (where, port, 0, 0)
s = socket.socket(af, socket.SOCK_STREAM, 0)
try:
expiration = _compute_expiration(timeout)
s.setblocking(0)
_connect(s, destination)
l = len(wire)
# copying the wire into tcpmsg is inefficient, but lets us
# avoid writev() or doing a short write that would get pushed
# onto the net
tcpmsg = struct.pack("!H", l) + wire
_net_write(s, tcpmsg, expiration)
ldata = _net_read(s, 2, expiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(s, l, expiration)
finally:
s.close()
r = linkcheck.dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac)
if not q.is_response(r):
raise BadResponse
return r
def xfr(where, zone, rdtype=linkcheck.dns.rdatatype.AXFR, rdclass=linkcheck.dns.rdataclass.IN,
timeout=None, port=53, keyring=None, keyname=None, relativize=True,
af=None, lifetime=None):
"""Return a generator for the responses to a zone transfer.
@param where: where to send the message
@type where: string
@param zone: The name of the zone to transfer
@type zone: linkcheck.dns.name.Name object or string
@param rdtype: The type of zone transfer. The default is
linkcheck.dns.rdatatype.AXFR.
@type rdtype: int or string
@param rdclass: The class of the zone transfer. The default is
linkcheck.dns.rdatatype.IN.
@type rdclass: int or string
@param timeout: The number of seconds to wait for each response message.
If None, the default, wait forever.
@type timeout: float
@param port: The port to which to send the message. The default is 53.
@type port: int
@param keyring: The TSIG keyring to use
@type keyring: dict
@param keyname: The name of the TSIG key to use
@type keyname: linkcheck.dns.name.Name object or string
@param relativize: If True, all names in the zone will be relativized to
the zone origin.
@type relativize: bool
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of of where.
If the inference attempt fails, AF_INET is used.
@type af: int
@param lifetime: The total number of seconds to spend doing the transfer.
If None, the default, then there is no limit on the time the transfer may
take.
@type lifetime: float
@rtype: generator of linkcheck.dns.message.Message objects."""
if isinstance(zone, str):
zone = linkcheck.dns.name.from_text(zone)
q = linkcheck.dns.message.make_query(zone, rdtype, rdclass)
if not keyring is None:
q.use_tsig(keyring, keyname)
wire = q.to_wire()
if af is None:
try:
af = linkcheck.dns.inet.af_for_address(where)
except:
af = linkcheck.dns.inet.AF_INET
if af == linkcheck.dns.inet.AF_INET:
destination = (where, port)
elif af == linkcheck.dns.inet.AF_INET6:
destination = (where, port, 0, 0)
s = socket.socket(af, socket.SOCK_STREAM, 0)
expiration = _compute_expiration(lifetime)
_connect(s, destination)
l = len(wire)
tcpmsg = struct.pack("!H", l) + wire
_net_write(s, tcpmsg, expiration)
done = False
seen_soa = False
if relativize:
origin = zone
oname = linkcheck.dns.name.empty
else:
origin = None
oname = zone
tsig_ctx = None
first = True
while not done:
mexpiration = _compute_expiration(timeout)
if mexpiration is None or mexpiration > expiration:
mexpiration = expiration
ldata = _net_read(s, 2, mexpiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(s, l, mexpiration)
r = linkcheck.dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
xfr=True, origin=origin, tsig_ctx=tsig_ctx,
multi=True, first=first)
tsig_ctx = r.tsig_ctx
first = False
if not seen_soa:
if not r.answer or r.answer[0].name != oname:
raise linkcheck.dns.exception.FormError
rrset = r.answer[0]
if rrset.rdtype != linkcheck.dns.rdatatype.SOA:
raise linkcheck.dns.exception.FormError
seen_soa = True
if len(r.answer) > 1 and r.answer[-1].name == oname:
rrset = r.answer[-1]
if rrset.rdtype == linkcheck.dns.rdatatype.SOA:
if q.keyring and not r.had_tsig:
raise linkcheck.dns.exception.FormError, "missing TSIG"
done = True
elif r.answer and r.answer[-1].name == oname:
rrset = r.answer[-1]
if rrset.rdtype == linkcheck.dns.rdatatype.SOA:
if q.keyring and not r.had_tsig:
raise linkcheck.dns.exception.FormError, "missing TSIG"
done = True
yield r
s.close()

120
linkcheck/dns/rcode.py Normal file
View file

@ -0,0 +1,120 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Result Codes."""
import linkcheck.dns.exception
NOERROR = 0
FORMERR = 1
SERVFAIL = 2
NXDOMAIN = 3
NOTIMP = 4
REFUSED = 5
YXDOMAIN = 6
YXRRSET = 7
NXRRSET = 8
NOTAUTH = 9
NOTZONE = 10
BADVERS = 16
_by_text = {
'NOERROR' : NOERROR,
'FORMERR' : FORMERR,
'SERVFAIL' : SERVFAIL,
'NXDOMAIN' : NXDOMAIN,
'NOTIMP' : NOTIMP,
'REFUSED' : REFUSED,
'YXDOMAIN' : YXDOMAIN,
'YXRRSET' : YXRRSET,
'NXRRSET' : NXRRSET,
'NOTAUTH' : NOTAUTH,
'NOTZONE' : NOTZONE,
'BADVERS' : BADVERS
}
# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be a true inverse.
_by_value = dict([(y, x) for x, y in _by_text.iteritems()])
class UnknownRcode(linkcheck.dns.exception.DNSException):
"""Raised if an rcode is unknown."""
pass
def from_text(text):
"""Convert text into an rcode.
@param text: the texual rcode
@type text: string
@raises UnknownRcode: the rcode is unknown
@rtype: int
"""
if text.isdigit():
v = int(text)
if v >= 0 and v <= 4095:
return v
v = _by_text.get(text.upper())
if v is None:
raise UnknownRcode
return v
def from_flags(flags, ednsflags):
"""Return the rcode value encoded by flags and ednsflags.
@param flags: the DNS flags
@type flags: int
@param ednsflags: the EDNS flags
@type ednsflags: int
@raises ValueError: rcode is < 0 or > 4095
@rtype: int
"""
value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
if value < 0 or value > 4095:
raise ValueError, 'rcode must be >= 0 and <= 4095'
return value
def to_flags(value):
"""Return a (flags, ednsflags) tuple which encodes the rcode.
@param value: the rcode
@type value: int
@raises ValueError: rcode is < 0 or > 4095
@rtype: (int, int) tuple
"""
if value < 0 or value > 4095:
raise ValueError, 'rcode must be >= 0 and <= 4095'
v = value & 0xf
ev = long(value & 0xff0) << 20
return (v, ev)
def to_text(value):
"""Convert rcode into text.
@param value: the rcode
@type value: int
@rtype: string
"""
text = _by_value.get(value)
if text is None:
text = str(value)
return text

441
linkcheck/dns/rdata.py Normal file
View file

@ -0,0 +1,441 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS rdata.
@var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
the module which implements that type.
@type _rdata_modules: dict
@var _module_prefix: The prefix to use when forming modules names. The
default is 'linkcheck.dns.rdtypes'. Changing this value will break the library.
@type _module_prefix: string
@var _hex_chunk: At most this many octets that will be represented in each
chunk of hexstring that _hexify() produces before whitespace occurs.
@type _hex_chunk: int"""
import linkcheck.dns.exception
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
import linkcheck.dns.tokenizer
_hex_chunksize = 32
def _hexify(data, chunksize=None):
"""Convert a binary string into its hex encoding, broken up into chunks
of I{chunksize} characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is L{linkcheck.dns.rdata._hex_chunksize}
@rtype: string
"""
if chunksize is None:
chunksize = _hex_chunksize
hex = data.encode('hex_codec')
l = len(hex)
if l > chunksize:
chunks = []
i = 0
while i < l:
chunks.append(hex[i : i + chunksize])
i += chunksize
hex = ' '.join(chunks)
return hex
_base64_chunksize = 32
def _base64ify(data, chunksize=None):
"""Convert a binary string into its base64 encoding, broken up into chunks
of I{chunksize} characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is
L{linkcheck.dns.rdata._base64_chunksize}
@rtype: string
"""
if chunksize is None:
chunksize = _base64_chunksize
b64 = data.encode('base64_codec')
b64 = b64.replace('\n', '')
l = len(b64)
if l > chunksize:
chunks = []
i = 0
while i < l:
chunks.append(b64[i : i + chunksize])
i += chunksize
b64 = ' '.join(chunks)
return b64
__escaped = {
'"' : True,
'\\' : True,
}
def _escapify(qstring):
"""Escape the characters in a quoted string which need it.
@param qstring: the string
@type qstring: string
@returns: the escaped string
@rtype: string
"""
text = ''
for c in qstring:
if c in __escaped:
text += '\\' + c
elif ord(c) >= 0x20 and ord(c) < 0x7F:
text += c
else:
text += '\\%03d' % ord(c)
return text
def _truncate_bitmap(what):
"""Determine the index of greatest byte that isn't all zeros, and
return the bitmap that contains all the bytes less than that index.
@param what: a string of octets representing a bitmap.
@type what: string
@rtype: string
"""
for i in xrange(len(what) - 1, -1, -1):
if what[i] != '\x00':
break
return ''.join(what[0 : i + 1])
class Rdata(object):
"""Base class for all DNS rdata types.
"""
__slots__ = ['rdclass', 'rdtype']
def __init__(self, rdclass, rdtype):
"""Initialize an rdata.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
"""
self.rdclass = rdclass
self.rdtype = rdtype
def covers(self):
"""DNS SIG/RRSIG rdatas apply to a specific type; this type is
returned by the covers() function. If the rdata type is not
SIG or RRSIG, linkcheck.dns.rdatatype.NONE is returned. This is useful when
creating rdatasets, allowing the rdataset to contain only RRSIGs
of a particular type, e.g. RRSIG(NS).
@rtype: int
"""
return linkcheck.dns.rdatatype.NONE
def extended_rdatatype(self):
"""Return a 32-bit type value, the least significant 16 bits of
which are the ordinary DNS type, and the upper 16 bits of which are
the "covered" type, if any.
@rtype: int
"""
return self.covers() << 16 | self.rdtype
def to_text(self, origin=None, relativize=True, **kw):
"""Convert an rdata to text format.
@rtype: string
"""
raise NotImplementedError
def to_wire(self, file, compress = None, origin = None):
"""Convert an rdata to wire format.
@rtype: string
"""
raise NotImplementedError
def __repr__(self):
covers = self.covers()
if covers == linkcheck.dns.rdatatype.NONE:
ctext = ''
else:
ctext = '(' + linkcheck.dns.rdatatype.to_text(covers) + ')'
return '<DNS ' + linkcheck.dns.rdataclass.to_text(self.rdclass) + ' ' + \
linkcheck.dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
str(self) + '>'
def __str__(self):
return self.to_text()
def _cmp(self, other):
"""Compare an rdata with another rdata of the same rdtype and
rdclass. Return < 0 if self < other in the DNSSEC ordering,
0 if self == other, and > 0 if self > other.
"""
raise NotImplementedError
def __eq__(self, other):
if not isinstance(other, Rdata):
return False
if self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return False
return self._cmp(other) == 0
def __ne__(self, other):
if not isinstance(other, Rdata):
return True
if self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return True
return self._cmp(other) != 0
def __lt__(self, other):
if not isinstance(other, Rdata) or \
self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return NotImplemented
return self._cmp(other) < 0
def __le__(self, other):
if not isinstance(other, Rdata) or \
self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return NotImplemented
return self._cmp(other) <= 0
def __ge__(self, other):
if not isinstance(other, Rdata) or \
self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return NotImplemented
return self._cmp(other) >= 0
def __gt__(self, other):
if not isinstance(other, Rdata) or \
self.rdclass != other.rdclass or \
self.rdtype != other.rdtype:
return NotImplemented
return self._cmp(other) > 0
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
"""Build an rdata object from text format.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param tok: The tokenizer
@type tok: linkcheck.dns.tokenizer.Tokenizer
@param origin: The origin to use for relative names
@type origin: linkcheck.dns.name.Name
@param relativize: should names be relativized?
@type origin: bool
@rtype: linkcheck.dns.rdata.Rdata instance
"""
raise NotImplementedError
from_text = classmethod(from_text)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
"""Build an rdata object from wire format
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param wire: The wire-format message
@type wire: string
@param current: The offet in wire of the beginning of the rdata.
@type current: int
@param rdlen: The length of the wire-format rdata
@type rdlen: int
@param origin: The origin to use for relative names
@type origin: linkcheck.dns.name.Name
@rtype: linkcheck.dns.rdata.Rdata instance
"""
raise NotImplementedError
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
"""Convert any domain names in the rdata to the specified
relativization.
"""
pass
class GenericRdata(Rdata):
"""Generate Rdata Class
This class is used for rdata types for which we have no better
implementation. It implements the DNS "unknown RRs" scheme.
"""
__slots__ = ['data']
def __init__(self, rdclass, rdtype, data):
super(GenericRdata, self).__init__(rdclass, rdtype)
self.data = data
def to_text(self, origin=None, relativize=True, **kw):
return r'\# %d ' % len(self.data) + _hexify(self.data)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
if tok.get_string() != r'\#':
raise linkcheck.dns.exception.SyntaxError, \
r'generic rdata does not start with \#'
length = tok.get_int()
chunks = []
while 1:
(ttype, value) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
chunks.append(value)
hex = ''.join(chunks)
data = hex.decode('hex_codec')
if len(data) != length:
raise linkcheck.dns.exception.SyntaxError, \
'generic rdata hex data has wrong length'
return cls(rdclass, rdtype, data)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
file.write(self.data)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
return cls(rdclass, rdtype, wire[current : current + rdlen])
from_wire = classmethod(from_wire)
def _cmp(self, other):
return cmp(self.data, other.data)
_rdata_modules = {}
_module_prefix = 'linkcheck.dns.rdtypes'
def get_rdata_class(rdclass, rdtype):
def import_module(name):
mod = __import__(name)
components = name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return mod
mod = _rdata_modules.get((rdclass, rdtype))
rdclass_text = linkcheck.dns.rdataclass.to_text(rdclass)
rdtype_text = linkcheck.dns.rdatatype.to_text(rdtype)
rdtype_text = rdtype_text.replace('-', '_')
if not mod:
mod = _rdata_modules.get((linkcheck.dns.rdatatype.ANY, rdtype))
if not mod:
try:
mod = import_module('.'.join([_module_prefix,
rdclass_text, rdtype_text]))
_rdata_modules[(rdclass, rdtype)] = mod
except ImportError:
try:
mod = import_module('.'.join([_module_prefix,
'ANY', rdtype_text]))
_rdata_modules[(linkcheck.dns.rdataclass.ANY, rdtype)] = mod
except ImportError:
mod = None
if mod:
cls = getattr(mod, rdtype_text)
else:
cls = GenericRdata
return cls
def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
"""Build an rdata object from text format.
This function attempts to dynamically load a class which
implements the specified rdata class and type. If there is no
class-and-type-specific implementation, the GenericRdata class
is used.
Once a class is chosen, its from_text() class method is called
with the parameters to this function.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param tok: The tokenizer
@type tok: linkcheck.dns.tokenizer.Tokenizer
@param origin: The origin to use for relative names
@type origin: linkcheck.dns.name.Name
@param relativize: Should names be relativized?
@type relativize: bool
@rtype: linkcheck.dns.rdata.Rdata instance"""
if isinstance(tok, str):
tok = linkcheck.dns.tokenizer.Tokenizer(tok)
cls = get_rdata_class(rdclass, rdtype)
if cls != GenericRdata:
# peek at first token
token = tok.get()
tok.unget(token)
if token[0] == linkcheck.dns.tokenizer.IDENTIFIER and \
token[1] == r'\#':
#
# Known type using the generic syntax. Extract the
# wire form from the generic syntax, and then run
# from_wire on it.
#
rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
relativize)
return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
origin)
return cls.from_text(rdclass, rdtype, tok, origin, relativize)
def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
"""Build an rdata object from wire format
This function attempts to dynamically load a class which
implements the specified rdata class and type. If there is no
class-and-type-specific implementation, the GenericRdata class
is used.
Once a class is chosen, its from_wire() class method is called
with the parameters to this function.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param wire: The wire-format message
@type wire: string
@param current: The offet in wire of the beginning of the rdata.
@type current: int
@param rdlen: The length of the wire-format rdata
@type rdlen: int
@param origin: The origin to use for relative names
@type origin: linkcheck.dns.name.Name
@rtype: linkcheck.dns.rdata.Rdata instance"""
cls = get_rdata_class(rdclass, rdtype)
return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)

115
linkcheck/dns/rdataclass.py Normal file
View file

@ -0,0 +1,115 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Classes.
@var _by_text: The rdata class textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata class value to textual name mapping
@type _by_value: dict
@var _metaclasses: If an rdataclass is a metaclass, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metaclasses: dict"""
import re
import linkcheck.dns.exception
RESERVED0 = 0
IN = 1
CH = 3
HS = 4
NONE = 254
ANY = 255
_by_text = {
'RESERVED0' : RESERVED0,
'IN' : IN,
'CH' : CH,
'HS' : HS,
'NONE' : NONE,
'ANY' : ANY
}
# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse.
_by_value = dict([(y, x) for x, y in _by_text.iteritems()])
# Now that we've built the inverse map, we can add class aliases to
# the _by_text mapping.
_by_text.update({
'INTERNET' : IN,
'CHAOS' : CH,
'HESIOD' : HS
})
_metaclasses = {
NONE : True,
ANY : True
}
_unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I);
class UnknownRdataclass(linkcheck.dns.exception.DNSException):
"""Raised when a class is unknown."""
pass
def from_text(text):
"""Convert text into a DNS rdata class value.
@param text: the text
@type text: string
@rtype: int
@raises linkcheck.dns.rdataclass.UnknownRdataClass: the class is unknown
@raises ValueError: the rdata class value is not >= 0 and <= 65535
"""
value = _by_text.get(text.upper())
if value is None:
match = _unknown_class_pattern.match(text)
if match == None:
raise UnknownRdataclass
value = int(match.group(1))
if value < 0 or value > 65535:
raise ValueError, "class must be between >= 0 and <= 65535"
return value
def to_text(value):
"""Convert a DNS rdata class to text.
@param value: the rdata class value
@type value: int
@rtype: string
@raises ValueError: the rdata class value is not >= 0 and <= 65535
"""
if value < 0 or value > 65535:
raise ValueError, "class must be between >= 0 and <= 65535"
text = _by_value.get(value)
if text is None:
text = 'CLASS' + `value`
return text
def is_metaclass(rdclass):
"""True if the class is a metaclass.
@param rdclass: the rdata class
@type rdclass: int
@rtype: bool"""
if _metaclasses.has_key(rdclass):
return True
return False

325
linkcheck/dns/rdataset.py Normal file
View file

@ -0,0 +1,325 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS rdatasets (an rdataset is a set of rdatas of a given type and class)"""
import random
import StringIO
import struct
import sets
import linkcheck.dns.exception
import linkcheck.dns.rdatatype
import linkcheck.dns.rdataclass
import linkcheck.dns.rdata
import linkcheck.dns.set
class DifferingCovers(linkcheck.dns.exception.DNSException):
"""Raised if an attempt is made to add a SIG/RRSIG whose covered type
is not the same as that of the other rdatas in the rdataset."""
pass
class IncompatibleTypes(linkcheck.dns.exception.DNSException):
"""Raised if an attempt is made to add rdata of an incompatible type."""
pass
class Rdataset(linkcheck.dns.set.Set):
"""A DNS rdataset.
@ivar rdclass: The class of the rdataset
@type rdclass: int
@ivar rdtype: The type of the rdataset
@type rdtype: int
@ivar covers: The covered type. Usually this value is
linkcheck.dns.rdatatype.NONE, but if the rdtype is linkcheck.dns.rdatatype.SIG or
linkcheck.dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
@type covers: int
@ivar ttl: The DNS TTL (Time To Live) value
@type ttl: int
"""
__slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
def __init__(self, rdclass, rdtype, covers=linkcheck.dns.rdatatype.NONE):
"""Create a new rdataset of the specified class and type.
@see: the description of the class instance variables for the
meaning of I{rdclass} and I{rdtype}"""
super(Rdataset, self).__init__()
self.rdclass = rdclass
self.rdtype = rdtype
self.covers = covers
self.ttl = 0
def _clone(self):
obj = super(Rdataset, self)._clone()
obj.rdclass = self.rdclass
obj.rdtype = self.rdtype
obj.covers = self.covers
obj.ttl = self.ttl
return obj
def update_ttl(self, ttl):
"""Set the TTL of the rdataset to be the lesser of the set's current
TTL or the specified TTL. If the set contains no rdatas, set the TTL
to the specified TTL.
@param ttl: The TTL
@type ttl: int"""
if len(self) == 0:
self.ttl = ttl
elif ttl < self.ttl:
self.ttl = ttl
def add(self, rd, ttl=None):
"""Add the specified rdata to the rdataset.
If the optional I{ttl} parameter is supplied, then
self.update_ttl(ttl) will be called prior to adding the rdata.
@param rd: The rdata
@type rd: linkcheck.dns.rdata.Rdata object
@param ttl: The TTL
@type ttl: int"""
#
# If we're adding a signature, do some special handling to
# check that the signature covers the same type as the
# other rdatas in this rdataset. If this is the first rdata
# in the set, initialize the covers field.
#
if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype:
raise IncompatibleTypes
if not ttl is None:
self.update_ttl(ttl)
if self.rdtype == linkcheck.dns.rdatatype.RRSIG or \
self.rdtype == linkcheck.dns.rdatatype.SIG:
covers = rd.covers()
if len(self) == 0 and self.covers == linkcheck.dns.rdatatype.NONE:
self.covers = covers
elif self.covers != covers:
raise DifferingCovers
if linkcheck.dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0:
self.clear()
super(Rdataset, self).add(rd)
def union_update(self, other):
self.update_ttl(other.ttl)
super(Rdataset, self).union_update(other)
def intersection_update(self, other):
self.update_ttl(other.ttl)
super(Rdataset, self).intersection_update(other)
def update(self, other):
"""Add all rdatas in other to self.
@param other: The rdataset from which to update
@type other: linkcheck.dns.rdataset.Rdataset object"""
self.update_ttl(other.ttl)
super(Rdataset, self).update(other)
def __repr__(self):
if self.covers == 0:
ctext = ''
else:
ctext = '(' + linkcheck.dns.rdatatype.to_text(self.covers) + ')'
return '<DNS ' + linkcheck.dns.rdataclass.to_text(self.rdclass) + ' ' + \
linkcheck.dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>'
def __str__(self):
return self.to_text()
def __eq__(self, other):
"""Two rdatasets are equal if they have the same class, type, and
covers, and contain the same rdata.
@rtype: bool"""
if not isinstance(other, Rdataset):
return False
if self.rdclass != other.rdclass or \
self.rdtype != other.rdtype or \
self.covers != other.covers:
return False
return super(Rdataset, self).__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
def to_text(self, name=None, origin=None, relativize=True,
override_rdclass=None, **kw):
"""Convert the rdataset into DNS master file format.
@see: L{linkcheck.dns.name.Name.choose_relativity} for more information
on how I{origin} and I{relativize} determine the way names
are emitted.
Any additional keyword arguments are passed on to the rdata
to_text() method.
@param name: If name is not None, emit a RRs with I{name} as
the owner name.
@type name: linkcheck.dns.name.Name object
@param origin: The origin for relative names, or None.
@type origin: linkcheck.dns.name.Name object
@param relativize: True if names should names be relativized
@type relativize: bool"""
if not name is None:
name = name.choose_relativity(origin, relativize)
ntext = str(name)
pad = ' '
else:
ntext = ''
pad = ''
s = StringIO.StringIO()
if not override_rdclass is None:
rdclass = override_rdclass
else:
rdclass = self.rdclass
if len(self) == 0:
#
# Empty rdatasets are used for the question section, and in
# some dynamic updates, so we don't need to print out the TTL
# (which is meaningless anyway).
#
print >> s, '%s%s%s %s' % (ntext, pad,
linkcheck.dns.rdataclass.to_text(rdclass),
linkcheck.dns.rdatatype.to_text(self.rdtype))
else:
for rd in self:
print >> s, '%s%s%d %s %s %s' % \
(ntext, pad, self.ttl, linkcheck.dns.rdataclass.to_text(rdclass),
linkcheck.dns.rdatatype.to_text(self.rdtype),
rd.to_text(origin=origin, relativize=relativize, **kw))
#
# We strip off the final \n for the caller's convenience in printing
#
return s.getvalue()[:-1]
def to_wire(self, name, file, compress=None, origin=None,
override_rdclass=None, want_shuffle=True):
"""Convert the rdataset to wire format.
@param name: The owner name of the RRset that will be emitted
@type name: linkcheck.dns.name.Name object
@param file: The file to which the wire format data will be appended
@type file: file
@param compress: The compression table to use; the default is None.
@type compress: dict
@param origin: The origin to be appended to any relative names when
they are emitted. The default is None.
@returns: the number of records emitted
@rtype: int
"""
if not override_rdclass is None:
rdclass = override_rdclass
want_shuffle = False
else:
rdclass = self.rdclass
file.seek(0, 2)
if len(self) == 0:
name.to_wire(file, compress, origin)
stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
file.write(stuff)
return 1
else:
if want_shuffle:
l = list(self)
random.shuffle(l)
else:
l = self
for rd in l:
name.to_wire(file, compress, origin)
stuff = struct.pack("!HHIH", self.rdtype, rdclass,
self.ttl, 0)
file.write(stuff)
start = file.tell()
rd.to_wire(file, compress, origin)
end = file.tell()
assert end - start < 65536
file.seek(start - 2)
stuff = struct.pack("!H", end - start)
file.write(stuff)
file.seek(0, 2)
return len(self)
def match(self, rdclass, rdtype, covers):
"""Returns True if this rdataset matches the specified class, type,
and covers"""
return self.rdclass == rdclass and \
self.rdtype == rdtype and \
self.covers == covers
def from_text_list(rdclass, rdtype, ttl, text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with
the specified list of rdatas in text format.
@rtype: linkcheck.dns.rdataset.Rdataset object
"""
if isinstance(rdclass, str):
rdclass = linkcheck.dns.rdataclass.from_text(rdclass)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
r = Rdataset(rdclass, rdtype)
r.update_ttl(ttl)
for t in text_rdatas:
rd = linkcheck.dns.rdata.from_text(r.rdclass, r.rdtype, t)
r.add(rd)
return r
def from_text(rdclass, rdtype, ttl, *text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with
the specified rdatas in text format.
@rtype: linkcheck.dns.rdataset.Rdataset object
"""
return from_text_list(rdclass, rdtype, ttl, text_rdatas)
def from_rdata_list(ttl, rdatas):
"""Create an rdataset with the specified TTL, and with
the specified list of rdata objects.
@rtype: linkcheck.dns.rdataset.Rdataset object
"""
if len(rdatas) == 0:
raise ValueError, "rdata list must not be empty"
r = None
for rd in rdatas:
if r is None:
r = Rdataset(rd.rdclass, rd.rdtype)
r.update_ttl(ttl)
first_time = False
r.add(rd)
return r
def from_rdata(ttl, *rdatas):
"""Create an rdataset with the specified TTL, and with
the specified rdata objects.
@rtype: linkcheck.dns.rdataset.Rdataset object
"""
return from_rdata_list(ttl, rdatas)

216
linkcheck/dns/rdatatype.py Normal file
View file

@ -0,0 +1,216 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Types.
@var _by_text: The rdata type textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata type value to textual name mapping
@type _by_value: dict
@var _metatypes: If an rdatatype is a metatype, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metatypes: dict
@var _singletons: If an rdatatype is a singleton, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _singletons: dict"""
import re
import linkcheck.dns.exception
NONE = 0
A = 1
NS = 2
MD = 3
MF = 4
CNAME = 5
SOA = 6
MB = 7
MG = 8
MR = 9
NULL = 10
WKS = 11
PTR = 12
HINFO = 13
MINFO = 14
MX = 15
TXT = 16
RP = 17
AFSDB = 18
X25 = 19
ISDN = 20
RT = 21
NSAP = 22
NSAP_PTR = 23
SIG = 24
KEY = 25
PX = 26
GPOS = 27
AAAA = 28
LOC = 29
NXT = 30
SRV = 33
NAPTR = 35
KX = 36
CERT = 37
A6 = 38
DNAME = 39
OPT = 41
APL = 42
DS = 43
SSHFP = 44
RRSIG = 46
NSEC = 47
DNSKEY = 48
UNSPEC = 103
TKEY = 249
TSIG = 250
IXFR = 251
AXFR = 252
MAILB = 253
MAILA = 254
ANY = 255
_by_text = {
'NONE' : NONE,
'A' : A,
'NS' : NS,
'MD' : MD,
'MF' : MF,
'CNAME' : CNAME,
'SOA' : SOA,
'MB' : MB,
'MG' : MG,
'MR' : MR,
'NULL' : NULL,
'WKS' : WKS,
'PTR' : PTR,
'HINFO' : HINFO,
'MINFO' : MINFO,
'MX' : MX,
'TXT' : TXT,
'RP' : RP,
'AFSDB' : AFSDB,
'X25' : X25,
'ISDN' : ISDN,
'RT' : RT,
'NSAP' : NSAP,
'NSAP-PTR' : NSAP_PTR,
'SIG' : SIG,
'KEY' : KEY,
'PX' : PX,
'GPOS' : GPOS,
'AAAA' : AAAA,
'LOC' : LOC,
'NXT' : NXT,
'SRV' : SRV,
'NAPTR' : NAPTR,
'KX' : KX,
'CERT' : CERT,
'A6' : A6,
'DNAME' : DNAME,
'OPT' : OPT,
'APL' : APL,
'DS' : DS,
'SSHFP' : SSHFP,
'RRSIG' : RRSIG,
'NSEC' : NSEC,
'DNSKEY' : DNSKEY,
'UNSPEC' : UNSPEC,
'TKEY' : TKEY,
'TSIG' : TSIG,
'IXFR' : IXFR,
'AXFR' : AXFR,
'MAILB' : MAILB,
'MAILA' : MAILA,
'ANY' : ANY
}
# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse.
_by_value = dict([(y, x) for x, y in _by_text.iteritems()])
_metatypes = {
OPT : True
}
_singletons = {
SOA : True,
NXT : True,
DNAME : True,
# CNAME is technically a singleton, but we allow multiple CNAMEs.
}
_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I);
class UnknownRdatatype(linkcheck.dns.exception.DNSException):
"""Raised if a type is unknown."""
pass
def from_text(text):
"""Convert text into a DNS rdata type value.
@param text: the text
@type text: string
@raises linkcheck.dns.rdatatype.UnknownRdatatype: the type is unknown
@raises ValueError: the rdata type value is not >= 0 and <= 65535
@rtype: int"""
value = _by_text.get(text.upper())
if value is None:
match = _unknown_type_pattern.match(text)
if match == None:
raise UnknownRdatatype
value = int(match.group(1))
if value < 0 or value > 65535:
raise ValueError, "type must be between >= 0 and <= 65535"
return value
def to_text(value):
"""Convert a DNS rdata type to text.
@param value: the rdata type value
@type value: int
@raises ValueError: the rdata type value is not >= 0 and <= 65535
@rtype: string"""
if value < 0 or value > 65535:
raise ValueError, "type must be between >= 0 and <= 65535"
text = _by_value.get(value)
if text is None:
text = 'TYPE' + `value`
return text
def is_metatype(rdtype):
"""True if the type is a metatype.
@param rdtype: the type
@type rdtype: int
@rtype: bool"""
if rdtype >= TKEY and rdtype <= ANY or _metatypes.has_key(rdtype):
return True
return False
def is_singleton(rdtype):
"""True if the type is a singleton.
@param rdtype: the type
@type rdtype: int
@rtype: bool"""
if _singletons.has_key(rdtype):
return True
return False

View file

@ -0,0 +1,52 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.mxbase
class AFSDB(linkcheck.dns.rdtypes.mxbase.UncompressedMX):
"""AFSDB record
@ivar subtype: the subtype value
@type subtype: int
@ivar hostname: the hostname name
@type hostname: linkcheck.dns.name.Name object"""
# Use the property mechanism to make "subtype" an alias for the
# "preference" attribute, and "hostname" an alias for the "exchange"
# attribute.
#
# This lets us inherit the UncompressedMX implementation but lets
# the caller use appropriate attribute names for the rdata type.
#
# We probably lose some performance vs. a cut-and-paste
# implementation, but this way we don't copy code, and that's
# good.
def get_subtype(self):
return self.preference
def set_subtype(self, subtype):
self.preference = subtype
subtype = property(get_subtype, set_subtype)
def get_hostname(self):
return self.exchange
def set_hostname(self, hostname):
self.exchange = hostname
hostname = property(get_hostname, set_hostname)

View file

@ -0,0 +1,132 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO
import struct
import linkcheck.dns.exception
import linkcheck.dns.dnssec
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
_ctype_by_value = {
1 : 'PKIX',
2 : 'SPKI',
3 : 'PGP',
253 : 'URI',
254 : 'OID',
}
_ctype_by_name = {
'PKIX' : 1,
'SPKI' : 2,
'PGP' : 3,
'URI' : 253,
'OID' : 254,
}
def _ctype_from_text(what):
v = _ctype_by_name.get(what)
if not v is None:
return v
return int(what)
def _ctype_to_text(what):
v = _ctype_by_value.get(what)
if not v is None:
return v
return str(what)
class CERT(linkcheck.dns.rdata.Rdata):
"""CERT record
@ivar certificate_type: certificate type
@type certificate_type: int
@ivar key_tag: key tag
@type key_tag: int
@ivar algorithm: algorithm
@type algorithm: int
@ivar certificate: the certificate or CRL
@type certificate: string
@see: RFC 2538"""
__slots__ = ['certificate_type', 'key_tag', 'algorithm', 'certificate']
def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm,
certificate):
super(CERT, self).__init__(rdclass, rdtype)
self.certificate_type = certificate_type
self.key_tag = key_tag
self.algorithm = algorithm
self.certificate = certificate
def to_text(self, origin=None, relativize=True, **kw):
certificate_type = _ctype_to_text(self.certificate_type)
return "%s %d %s %s" % (certificate_type, self.key_tag,
linkcheck.dns.dnssec.algorithm_to_text(self.algorithm),
linkcheck.dns.rdata._base64ify(self.certificate))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
certificate_type = _ctype_from_text(tok.get_string())
key_tag = tok.get_uint16()
algorithm = linkcheck.dns.dnssec.algorithm_from_text(tok.get_string())
if algorithm < 0 or algorithm > 255:
raise linkcheck.dns.exception.SyntaxError, "bad algorithm type"
chunks = []
while 1:
t = tok.get()
if t[0] == linkcheck.dns.tokenizer.EOL or t[0] == linkcheck.dns.tokenizer.EOF:
break
if t[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
chunks.append(t[1])
b64 = ''.join(chunks)
certificate = b64.decode('base64_codec')
return cls(rdclass, rdtype, certificate_type, key_tag,
algorithm, certificate)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
prefix = struct.pack("!HHB", self.certificate_type, self.key_tag,
self.algorithm)
file.write(prefix)
file.write(self.certificate)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
prefix = wire[current : current + 5]
current += 5
rdlen -= 5
if rdlen < 0:
raise linkcheck.dns.exception.FormError
(certificate_type, key_tag, algorithm) = struct.unpack("!HHB", prefix)
certificate = wire[current : current + rdlen]
return cls(rdclass, rdtype, certificate_type, key_tag, algorithm,
certificate)
from_wire = classmethod(from_wire)
def _cmp(self, other):
f = cStringIO.StringIO()
self.to_wire(f)
wire1 = f.getvalue()
f.seek(0)
f.truncate()
other.to_wire(f)
wire2 = f.getvalue()
f.close()
return cmp(wire1, wire2)

View file

@ -0,0 +1,25 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.nsbase
class CNAME(linkcheck.dns.rdtypes.nsbase.NSBase):
"""CNAME record
Note: although CNAME is officially a singleton type, dnspython allows
non-singleton CNAME rdatasets because such sets have been commonly
used by BIND and other nameservers for load balancing."""
pass

View file

@ -0,0 +1,22 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.nsbase
class DNAME(linkcheck.dns.rdtypes.nsbase.UncompressedNS):
"""DNAME record"""
pass

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.keybase
class DNSKEY(linkcheck.dns.rdtypes.keybase.KEYBase):
"""DNSKEY record"""
pass

View file

@ -0,0 +1,86 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.rdata
import linkcheck.dns.rdatatype
class DS(linkcheck.dns.rdata.Rdata):
"""DS record
@ivar key_tag: the key tag
@type key_tag: int
@ivar algorithm: the algorithm
@type algorithm: int
@ivar digest_type: the digest type
@type digest_type: int
@ivar digest: the digest
@type digest: int
@see: draft-ietf-dnsext-delegation-signer-14.txt"""
__slots__ = ['key_tag', 'algorithm', 'digest_type', 'digest']
def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type,
digest):
super(DS, self).__init__(rdclass, rdtype)
self.key_tag = key_tag
self.algorithm = algorithm
self.digest_type = digest_type
self.digest = digest
def to_text(self, origin=None, relativize=True, **kw):
return '%d %d %d %s' % (self.key_tag, self.algorithm,
self.digest_type,
linkcheck.dns.rdata._hexify(self.digest,
chunksize=128))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
key_tag = tok.get_uint16()
algorithm = tok.get_uint8()
digest_type = tok.get_uint8()
digest = tok.get_string()
digest = digest.decode('hex_codec')
tok.get_eol()
return cls(rdclass, rdtype, key_tag, algorithm, digest_type,
digest)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
header = struct.pack("!HBB", self.key_tag, self.algorithm,
self.digest_type)
file.write(header)
file.write(self.digest)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
header = struct.unpack("!HBB", wire[current : current + 4])
current += 4
rdlen -= 4
digest = wire[current : current + rdlen]
return cls(rdclass, rdtype, header[0], header[1], header[2], digest)
from_wire = classmethod(from_wire)
def _cmp(self, other):
hs = struct.pack("!HBB", self.key_tag, self.algorithm,
self.digest_type)
ho = struct.pack("!HBB", other.key_tag, other.algorithm,
other.digest_type)
v = cmp(hs, ho)
if v == 0:
v = cmp(self.digest, other.digest)
return v

View file

@ -0,0 +1,157 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
def _validate_float_string(what):
if what[0] == '-' or what[0] == '+':
what = what[1:]
if what.isdigit():
return
(left, right) = what.split('.')
if left == '' and right == '':
raise linkcheck.dns.exception.FormError
if not left == '' and not left.isdigit():
raise linkcheck.dns.exception.FormError
if not right == '' and not right.isdigit():
raise linkcheck.dns.exception.FormError
class GPOS(linkcheck.dns.rdata.Rdata):
"""GPOS record
@ivar latitude: latitude
@type latitude: string
@ivar longitude: longitude
@type longitude: string
@ivar altitude: altitude
@type altitude: string
@see: RFC 1712"""
__slots__ = ['latitude', 'longitude', 'altitude']
def __init__(self, rdclass, rdtype, latitude, longitude, altitude):
super(GPOS, self).__init__(rdclass, rdtype)
if isinstance(latitude, float) or \
isinstance(latitude, int) or \
isinstance(latitude, long):
latitude = str(latitude)
if isinstance(longitude, float) or \
isinstance(longitude, int) or \
isinstance(longitude, long):
longitude = str(longitude)
if isinstance(altitude, float) or \
isinstance(altitude, int) or \
isinstance(altitude, long):
altitude = str(altitude)
_validate_float_string(latitude)
_validate_float_string(longitude)
_validate_float_string(altitude)
self.latitude = latitude
self.longitude = longitude
self.altitude = altitude
def to_text(self, origin=None, relativize=True, **kw):
return '%s %s %s' % (self.latitude, self.longitude, self.altitude)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
latitude = tok.get_string()
longitude = tok.get_string()
altitude = tok.get_string()
tok.get_eol()
return cls(rdclass, rdtype, latitude, longitude, altitude)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
l = len(self.latitude)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.latitude)
l = len(self.longitude)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.longitude)
l = len(self.altitude)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.altitude)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen:
raise linkcheck.dns.exception.FormError
latitude = wire[current : current + l]
current += l
rdlen -= l
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen:
raise linkcheck.dns.exception.FormError
longitude = wire[current : current + l]
current += l
rdlen -= l
l = ord(wire[current])
current += 1
rdlen -= 1
if l != rdlen:
raise linkcheck.dns.exception.FormError
altitude = wire[current : current + l]
return cls(rdclass, rdtype, latitude, longitude, altitude)
from_wire = classmethod(from_wire)
def _cmp(self, other):
v = cmp(self.latitude, other.latitude)
if v == 0:
v = cmp(self.longitude, other.longitude)
if v == 0:
v = cmp(self.altitude, other.altitude)
return v
def _get_float_latitude(self):
return float(self.latitude)
def _set_float_latitude(self, value):
self.latitude = str(value)
float_latitude = property(_get_float_latitude, _set_float_latitude,
doc="latitude as a floating point value")
def _get_float_longitude(self):
return float(self.longitude)
def _set_float_longitude(self, value):
self.longitude = str(value)
float_longitude = property(_get_float_longitude, _set_float_longitude,
doc="longitude as a floating point value")
def _get_float_altitude(self):
return float(self.altitude)
def _set_float_altitude(self, value):
self.altitude = str(value)
float_altitude = property(_get_float_altitude, _set_float_altitude,
doc="altitude as a floating point value")

View file

@ -0,0 +1,84 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class HINFO(linkcheck.dns.rdata.Rdata):
"""HINFO record
@ivar cpu: the CPU type
@type cpu: string
@ivar os: the OS type
@type os: string
@see: RFC 1035"""
__slots__ = ['cpu', 'os']
def __init__(self, rdclass, rdtype, cpu, os):
super(HINFO, self).__init__(rdclass, rdtype)
self.cpu = cpu
self.os = os
def to_text(self, origin=None, relativize=True, **kw):
return '"%s" "%s"' % (linkcheck.dns.rdata._escapify(self.cpu),
linkcheck.dns.rdata._escapify(self.os))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
cpu = tok.get_string()
os = tok.get_string()
tok.get_eol()
return cls(rdclass, rdtype, cpu, os)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
l = len(self.cpu)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.cpu)
l = len(self.os)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.os)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen:
raise linkcheck.dns.exception.FormError
cpu = wire[current : current + l]
current += l
rdlen -= l
l = ord(wire[current])
current += 1
rdlen -= 1
if l != rdlen:
raise linkcheck.dns.exception.FormError
os = wire[current : current + l]
return cls(rdclass, rdtype, cpu, os)
from_wire = classmethod(from_wire)
def _cmp(self, other):
v = cmp(self.cpu, other.cpu)
if v == 0:
v = cmp(self.os, other.os)
return v

View file

@ -0,0 +1,97 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class ISDN(linkcheck.dns.rdata.Rdata):
"""ISDN record
@ivar address: the ISDN address
@type address: string
@ivar subaddress: the ISDN subaddress (or '' if not present)
@type subaddress: string
@see: RFC 1183"""
__slots__ = ['address', 'subaddress']
def __init__(self, rdclass, rdtype, address, subaddress):
super(ISDN, self).__init__(rdclass, rdtype)
self.address = address
self.subaddress = subaddress
def to_text(self, origin=None, relativize=True, **kw):
if self.subaddress:
return '"%s" "%s"' % (linkcheck.dns.rdata._escapify(self.address),
linkcheck.dns.rdata._escapify(self.subaddress))
else:
return '"%s"' % linkcheck.dns.rdata._escapify(self.address)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
address = tok.get_string()
t = tok.get()
if t[0] != linkcheck.dns.tokenizer.EOL and t[0] != linkcheck.dns.tokenizer.EOF:
tok.unget(t)
subaddress = tok.get_string()
else:
tok.unget(t)
subaddress = ''
tok.get_eol()
return cls(rdclass, rdtype, address, subaddress)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
l = len(self.address)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.address)
l = len(self.subaddress)
if l > 0:
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.subaddress)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen:
raise linkcheck.dns.exception.FormError
address = wire[current : current + l]
current += l
rdlen -= l
if rdlen > 0:
l = ord(wire[current])
current += 1
rdlen -= 1
if l != rdlen:
raise linkcheck.dns.exception.FormError
subaddress = wire[current : current + l]
else:
subaddress = ''
return cls(rdclass, rdtype, address, subaddress)
from_wire = classmethod(from_wire)
def _cmp(self, other):
v = cmp(self.address, other.address)
if v == 0:
v = cmp(self.subaddress, other.subaddress)
return v

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.keybase
class KEY(linkcheck.dns.rdtypes.keybase.KEYBase):
"""KEY record"""
pass

View file

@ -0,0 +1,342 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdata
_pows = (1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L,
100000000L, 1000000000L, 10000000000L)
def _exponent_of(what, desc):
exp = None
for i in xrange(len(_pows)):
if what // _pows[i] == 0L:
exp = i - 1
break
if exp is None or exp < 0:
raise linkcheck.dns.exception.SyntaxError, "%s value out of bounds" % desc
return exp
def _float_to_tuple(what):
if what < 0:
sign = -1
what *= -1
else:
sign = 1
what = long(round(what * 3600000))
degrees = int(what // 3600000)
what -= degrees * 3600000
minutes = int(what // 60000)
what -= minutes * 60000
seconds = int(what // 1000)
what -= int(seconds * 1000)
what = int(what)
return (degrees * sign, minutes, seconds, what)
def _tuple_to_float(what):
if what[0] < 0:
sign = -1
value = float(what[0]) * -1
else:
sign = 1
value = float(what[0])
value += float(what[1]) / 60.0
value += float(what[2]) / 3600.0
value += float(what[3]) / 3600000.0
return value
def _encode_size(what, desc):
what = long(what);
exponent = _exponent_of(what, desc) & 0xF
base = what // pow(10, exponent) & 0xF
return base * 16 + exponent
def _decode_size(what, desc):
exponent = what & 0x0F
if exponent > 9:
raise linkcheck.dns.exception.SyntaxError, "bad %s exponent" % desc
base = (what & 0xF0) >> 4
if base > 9:
raise linkcheck.dns.exception.SyntaxError, "bad %s base" % desc
return long(base) * pow(10, exponent)
class LOC(linkcheck.dns.rdata.Rdata):
"""LOC record
@ivar latitude: latitude
@type latitude: (int, int, int, int) tuple specifying the degrees, minutes,
seconds, and milliseconds of the coordinate.
@ivar longitude: longitude
@type longitude: (int, int, int, int) tuple specifying the degrees,
minutes, seconds, and milliseconds of the coordinate.
@ivar altitude: altitude
@type altitude: float
@ivar size: size of the sphere
@type size: float
@ivar horizontal_precision: horizontal precision
@type horizontal_precision: float
@ivar vertical_precision: vertical precision
@type vertical_precision: float
@see: RFC 1876"""
__slots__ = ['latitude', 'longitude', 'altitude', 'size',
'horizontal_precision', 'vertical_precision']
def __init__(self, rdclass, rdtype, latitude, longitude, altitude,
size=1.0, hprec=10000.0, vprec=10.0):
"""Initialize a LOC record instance.
The parameters I{latitude} and I{longitude} may be either a 4-tuple
of integers specifying (degrees, minutes, seconds, milliseconds),
or they may be floating point values specifying the number of
degrees. The other parameters are floats."""
super(LOC, self).__init__(rdclass, rdtype)
if isinstance(latitude, int) or isinstance(latitude, long):
latitude = float(latitude)
if isinstance(latitude, float):
latitude = _float_to_tuple(latitude)
self.latitude = latitude
if isinstance(longitude, int) or isinstance(longitude, long):
longitude = float(longitude)
if isinstance(longitude, float):
longitude = _float_to_tuple(longitude)
self.longitude = longitude
self.altitude = float(altitude)
self.size = float(size)
self.horizontal_precision = float(hprec)
self.vertical_precision = float(vprec)
def to_text(self, origin=None, relativize=True, **kw):
if self.latitude[0] > 0:
lat_hemisphere = 'N'
lat_degrees = self.latitude[0]
else:
lat_hemisphere = 'S'
lat_degrees = -1 * self.latitude[0]
if self.longitude[0] > 0:
long_hemisphere = 'E'
long_degrees = self.longitude[0]
else:
long_hemisphere = 'W'
long_degrees = -1 * self.longitude[0]
text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % (
lat_degrees, self.latitude[1], self.latitude[2], self.latitude[3],
lat_hemisphere, long_degrees, self.longitude[1], self.longitude[2],
self.longitude[3], long_hemisphere, self.altitude / 100.0
)
if self.size != 1.0 or self.horizontal_precision != 10000.0 or \
self.vertical_precision != 10.0:
text += " %0.2fm %0.2fm %0.2fm" % (
self.size / 100.0, self.horizontal_precision / 100.0,
self.vertical_precision / 100.0
)
return text
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
latitude = [0, 0, 0, 0]
longitude = [0, 0, 0, 0]
size = 1.0
hprec = 10000.0
vprec = 10.0
latitude[0] = tok.get_int()
t = tok.get_string()
if t.isdigit():
latitude[1] = int(t)
t = tok.get_string()
if '.' in t:
(seconds, milliseconds) = t.split('.')
if not seconds.isdigit():
raise linkcheck.dns.exception.SyntaxError, \
'bad latitude seconds value'
latitude[2] = int(seconds)
if latitude[2] >= 60:
raise linkcheck.dns.exception.SyntaxError, \
'latitude seconds >= 60'
l = len(milliseconds)
if l == 0 or l > 3 or not milliseconds.isdigit():
raise linkcheck.dns.exception.SyntaxError, \
'bad latitude milliseconds value'
if l == 1:
m = 100
elif l == 2:
m = 10
else:
m = 1
latitude[3] = m * int(milliseconds)
t = tok.get_string()
elif t.isdigit():
latitude[2] = int(t)
t = tok.get_string()
if t == 'S':
latitude[0] *= -1
elif t != 'N':
raise linkcheck.dns.exception.SyntaxError, 'bad latitude hemisphere value'
longitude[0] = tok.get_int()
t = tok.get_string()
if t.isdigit():
longitude[1] = int(t)
t = tok.get_string()
if '.' in t:
(seconds, milliseconds) = t.split('.')
if not seconds.isdigit():
raise linkcheck.dns.exception.SyntaxError, \
'bad longitude seconds value'
longitude[2] = int(seconds)
if longitude[2] >= 60:
raise linkcheck.dns.exception.SyntaxError, \
'longitude seconds >= 60'
l = len(milliseconds)
if l == 0 or l > 3 or not milliseconds.isdigit():
raise linkcheck.dns.exception.SyntaxError, \
'bad longitude milliseconds value'
if l == 1:
m = 100
elif l == 2:
m = 10
else:
m = 1
longitude[3] = m * int(milliseconds)
t = tok.get_string()
elif t.isdigit():
longitude[2] = int(t)
t = tok.get_string()
if t == 'W':
longitude[0] *= -1
elif t != 'E':
raise linkcheck.dns.exception.SyntaxError, 'bad longitude hemisphere value'
t = tok.get_string()
if t[-1] == 'm':
t = t[0 : -1]
altitude = float(t) * 100.0 # m -> cm
(ttype, value) = tok.get()
if ttype != linkcheck.dns.tokenizer.EOL and ttype != linkcheck.dns.tokenizer.EOF:
if value[-1] == 'm':
value = value[0 : -1]
size = float(value) * 100.0 # m -> cm
(ttype, value) = tok.get()
if ttype != linkcheck.dns.tokenizer.EOL and ttype != linkcheck.dns.tokenizer.EOF:
if value[-1] == 'm':
value = value[0 : -1]
hprec = float(value) * 100.0 # m -> cm
(ttype, value) = tok.get()
if ttype != linkcheck.dns.tokenizer.EOL and ttype != linkcheck.dns.tokenizer.EOF:
if value[-1] == 'm':
value = value[0 : -1]
vprec = float(value) * 100.0 # m -> cm
(ttype, value) = tok.get()
if ttype != linkcheck.dns.tokenizer.EOL and \
ttype != linkcheck.dns.tokenizer.EOF:
raise linkcheck.dns.exception.SyntaxError, \
"expected EOL or EOF"
return cls(rdclass, rdtype, latitude, longitude, altitude,
size, hprec, vprec)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
if self.latitude[0] < 0:
sign = -1
degrees = long(-1 * self.latitude[0])
else:
sign = 1
degrees = long(self.latitude[0])
milliseconds = (degrees * 3600000 +
self.latitude[1] * 60000 +
self.latitude[2] * 1000 +
self.latitude[3]) * sign
latitude = 0x80000000L + milliseconds
if self.longitude[0] < 0:
sign = -1
degrees = long(-1 * self.longitude[0])
else:
sign = 1
degrees = long(self.longitude[0])
milliseconds = (degrees * 3600000 +
self.longitude[1] * 60000 +
self.longitude[2] * 1000 +
self.longitude[3]) * sign
longitude = 0x80000000L + milliseconds
altitude = long(self.altitude) + 10000000L
size = _encode_size(self.size, "size")
hprec = _encode_size(self.horizontal_precision, "horizontal precision")
vprec = _encode_size(self.vertical_precision, "vertical precision")
wire = struct.pack("!BBBBIII", 0, size, hprec, vprec, latitude,
longitude, altitude)
file.write(wire)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(version, size, hprec, vprec, latitude, longitude, altitude) = \
struct.unpack("!BBBBIII", wire[current : current + rdlen])
if latitude > 0x80000000L:
latitude = float(latitude - 0x80000000L) / 3600000
else:
latitude = -1 * float(0x80000000L - latitude) / 3600000
if latitude < -90.0 or latitude > 90.0:
raise linkcheck.dns.exception.FormError, "bad latitude"
if longitude > 0x80000000L:
longitude = float(longitude - 0x80000000L) / 3600000
else:
longitude = -1 * float(0x80000000L - longitude) / 3600000
if longitude < -180.0 or longitude > 180.0:
raise linkcheck.dns.exception.FormError, "bad longitude"
altitude = float(altitude) - 10000000.0
size = _decode_size(size, "size")
hprec = _decode_size(hprec, "horizontal precision")
vprec = _decode_size(vprec, "vertical precision")
return cls(rdclass, rdtype, latitude, longitude, altitude,
size, hprec, vprec)
from_wire = classmethod(from_wire)
def _cmp(self, other):
f = cStringIO.StringIO()
self.to_wire(f)
wire1 = f.getvalue()
f.seek(0)
f.truncate()
other.to_wire(f)
wire2 = f.getvalue()
f.close()
return cmp(wire1, wire2)
def _get_float_latitude(self):
return _tuple_to_float(self.latitude)
def _set_float_latitude(self, value):
self.latitude = _float_to_tuple(value)
float_latitude = property(_get_float_latitude, _set_float_latitude,
doc="latitude as a floating point value")
def _get_float_longitude(self):
return _tuple_to_float(self.longitude)
def _set_float_longitude(self, value):
self.longitude = _float_to_tuple(value)
float_longitude = property(_get_float_longitude, _set_float_longitude,
doc="longitude as a floating point value")

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.mxbase
class MX(linkcheck.dns.rdtypes.mxbase.MXBase):
"""MX record"""
pass

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.nsbase
class NS(linkcheck.dns.rdtypes.nsbase.NSBase):
"""NS record"""
pass

View file

@ -0,0 +1,141 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.rdatatype
import linkcheck.dns.name
class NSEC(linkcheck.dns.rdata.Rdata):
"""NSEC record
@ivar next: the next name
@type next: linkcheck.dns.name.Name object
@ivar windows: the windowed bitmap list
@type windows: list of (window number, string) tuples"""
__slots__ = ['next', 'windows']
def __init__(self, rdclass, rdtype, next, windows):
super(NSEC, self).__init__(rdclass, rdtype)
self.next = next
self.windows = windows
def to_text(self, origin=None, relativize=True, **kw):
next = self.next.choose_relativity(origin, relativize)
for (window, bitmap) in self.windows:
bits = []
for i in xrange(0, len(bitmap)):
byte = ord(bitmap[i])
for j in xrange(0, 8):
if byte & (0x80 >> j):
bits.append(linkcheck.dns.rdatatype.to_text(window * 256 + \
i * 8 + j))
text = ' '.join(bits)
return '%s %s' % (next, text)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
next = tok.get_name()
next = next.choose_relativity(origin, relativize)
rdtypes = []
while 1:
(ttype, value) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
nrdtype = linkcheck.dns.rdatatype.from_text(value)
if nrdtype == 0:
raise linkcheck.dns.exception.SyntaxError, "NSEC with bit 0"
if nrdtype > 65535:
raise linkcheck.dns.exception.SyntaxError, "NSEC with bit > 65535"
rdtypes.append(nrdtype)
rdtypes.sort()
window = 0
octets = 0
prior_rdtype = 0
bitmap = ['\0'] * 32
windows = []
for nrdtype in rdtypes:
if nrdtype == prior_rdtype:
continue
prior_rdtype = nrdtype
new_window = nrdtype // 256
if new_window != window:
windows.append((window, ''.join(bitmap[0:octets])))
bitmap = ['\0'] * 32
window = new_window
offset = nrdtype % 256
byte = offset / 8
bit = offset % 8
octets = byte + 1
bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit))
windows.append((window, ''.join(bitmap[0:octets])))
return cls(rdclass, rdtype, next, windows)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
self.next.to_wire(file, None, origin)
for (window, bitmap) in self.windows:
file.write(chr(window))
file.write(chr(len(bitmap)))
file.write(bitmap)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(next, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen], current)
current += cused
rdlen -= cused
windows = []
while rdlen > 0:
if rdlen < 3:
raise linkcheck.dns.exception.FormError, "NSEC too short"
window = ord(wire[current])
octets = ord(wire[current + 1])
if octets == 0 or octets > 32:
raise linkcheck.dns.exception.FormError, "bad NSEC octets"
current += 2
rdlen -= 2
if rdlen < octets:
raise linkcheck.dns.exception.FormError, "bad NSEC bitmap length"
bitmap = wire[current : current + octets]
current += octets
rdlen -= octets
windows.append((window, bitmap))
if not origin is None:
next = next.relativize(origin)
return cls(rdclass, rdtype, next, windows)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.next = self.next.choose_relativity(origin, relativize)
def _cmp(self, other):
v = cmp(self.next, other.next)
if v == 0:
b1 = cStringIO.StringIO()
for (window, bitmap) in self.windows:
b1.write(chr(window))
b1.write(chr(len(bitmap)))
b1.write(bitmap)
b2 = cStringIO.StringIO()
for (window, bitmap) in other.windows:
b2.write(chr(window))
b2.write(chr(len(bitmap)))
b2.write(bitmap)
v = cmp(b1.getvalue(), b2.getvalue())
return v

View file

@ -0,0 +1,97 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.rdatatype
import linkcheck.dns.name
class NXT(linkcheck.dns.rdata.Rdata):
"""NXT record
@ivar next: the next name
@type next: linkcheck.dns.name.Name object
@ivar bitmap: the type bitmap
@type bitmap: string
@see: RFC 2535"""
__slots__ = ['next', 'bitmap']
def __init__(self, rdclass, rdtype, next, bitmap):
super(NXT, self).__init__(rdclass, rdtype)
self.next = next
self.bitmap = bitmap
def to_text(self, origin=None, relativize=True, **kw):
next = self.next.choose_relativity(origin, relativize)
bits = []
for i in xrange(0, len(self.bitmap)):
byte = ord(self.bitmap[i])
for j in xrange(0, 8):
if byte & (0x80 >> j):
bits.append(linkcheck.dns.rdatatype.to_text(i * 8 + j))
text = ' '.join(bits)
return '%s %s' % (next, text)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
next = tok.get_name()
next = next.choose_relativity(origin, relativize)
bitmap = ['\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00' ]
while 1:
(ttype, value) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
if value.isdigit():
nrdtype = int(value)
else:
nrdtype = linkcheck.dns.rdatatype.from_text(value)
if nrdtype == 0:
raise linkcheck.dns.exception.SyntaxError, "NXT with bit 0"
if nrdtype > 127:
raise linkcheck.dns.exception.SyntaxError, "NXT with bit > 127"
i = nrdtype // 8
bitmap[i] = chr(ord(bitmap[i]) | (0x80 >> (nrdtype % 8)))
bitmap = linkcheck.dns.rdata._truncate_bitmap(bitmap)
return cls(rdclass, rdtype, next, bitmap)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
self.next.to_wire(file, None, origin)
file.write(self.bitmap)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(next, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen], current)
current += cused
rdlen -= cused
bitmap = wire[current : current + rdlen]
if not origin is None:
next = next.relativize(origin)
return cls(rdclass, rdtype, next, bitmap)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.next = self.next.choose_relativity(origin, relativize)
def _cmp(self, other):
v = cmp(self.next, other.next)
if v == 0:
v = cmp(self.bitmap, other.bitmap)
return v

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.nsbase
class PTR(linkcheck.dns.rdtypes.nsbase.NSBase):
"""PTR record"""
pass

View file

@ -0,0 +1,83 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class RP(linkcheck.dns.rdata.Rdata):
"""RP record
@ivar mbox: The responsible person's mailbox
@type mbox: linkcheck.dns.name.Name object
@ivar txt: The owner name of a node with TXT records, or the root name
if no TXT records are associated with this RP.
@type txt: linkcheck.dns.name.Name object
@see: RFC 1183"""
__slots__ = ['mbox', 'txt']
def __init__(self, rdclass, rdtype, mbox, txt):
super(RP, self).__init__(rdclass, rdtype)
self.mbox = mbox
self.txt = txt
def to_text(self, origin=None, relativize=True, **kw):
mbox = self.mbox.choose_relativity(origin, relativize)
txt = self.txt.choose_relativity(origin, relativize)
return "%s %s" % (str(mbox), str(txt))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
mbox = tok.get_name()
txt = tok.get_name()
mbox = mbox.choose_relativity(origin, relativize)
txt = txt.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, mbox, txt)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
self.mbox.to_wire(file, None, origin)
self.txt.to_wire(file, None, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(mbox, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
current += cused
rdlen -= cused
if rdlen <= 0:
raise linkcheck.dns.exception.FormError
(txt, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
mbox = mbox.relativize(origin)
txt = txt.relativize(origin)
return cls(rdclass, rdtype, mbox, txt)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.mbox = self.mbox.choose_relativity(origin, relativize)
self.txt = self.txt.choose_relativity(origin, relativize)
def _cmp(self, other):
v = cmp(self.mbox, other.mbox)
if v == 0:
v = cmp(self.txt, other.txt)
return v

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.sigbase
class RRSIG(linkcheck.dns.rdtypes.sigbase.SIGBase):
"""RRSIG record"""
pass

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.mxbase
class RT(linkcheck.dns.rdtypes.mxbase.UncompressedMX):
"""RT record"""
pass

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.sigbase
class SIG(linkcheck.dns.rdtypes.sigbase.SIGBase):
"""SIG record"""
pass

View file

@ -0,0 +1,122 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class SOA(linkcheck.dns.rdata.Rdata):
"""SOA record
@ivar mname: the SOA MNAME (master name) field
@type mname: linkcheck.dns.name.Name object
@ivar rname: the SOA RNAME (responsible name) field
@type rname: linkcheck.dns.name.Name object
@ivar serial: The zone's serial number
@type serial: int
@ivar refresh: The zone's refresh value (in seconds)
@type refresh: int
@ivar retry: The zone's retry value (in seconds)
@type retry: int
@ivar expiration: The zone's expiration value (in seconds)
@type expiration: int
@ivar minimum: The zone's negative caching time (in seconds, called
"minimum" for historical reasons)
@type minimum: int
@see: RFC 1035"""
__slots__ = ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire',
'minimum']
def __init__(self, rdclass, rdtype, mname, rname, serial, refresh, retry,
expire, minimum):
super(SOA, self).__init__(rdclass, rdtype)
self.mname = mname
self.rname = rname
self.serial = serial
self.refresh = refresh
self.retry = retry
self.expire = expire
self.minimum = minimum
def to_text(self, origin=None, relativize=True, **kw):
mname = self.mname.choose_relativity(origin, relativize)
rname = self.rname.choose_relativity(origin, relativize)
return '%s %s %d %d %d %d %d' % (
mname, rname, self.serial, self.refresh, self.retry,
self.expire, self.minimum )
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
mname = tok.get_name()
rname = tok.get_name()
mname = mname.choose_relativity(origin, relativize)
rname = rname.choose_relativity(origin, relativize)
serial = tok.get_uint32()
refresh = tok.get_uint32()
retry = tok.get_uint32()
expire = tok.get_uint32()
minimum = tok.get_uint32()
tok.get_eol()
return cls(rdclass, rdtype, mname, rname, serial, refresh, retry,
expire, minimum )
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
self.mname.to_wire(file, compress, origin)
self.rname.to_wire(file, compress, origin)
five_ints = struct.pack('!IIIII', self.serial, self.refresh,
self.retry, self.expire, self.minimum)
file.write(five_ints)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(mname, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen], current)
current += cused
rdlen -= cused
(rname, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen], current)
current += cused
rdlen -= cused
if rdlen != 20:
raise linkcheck.dns.exception.FormError
five_ints = struct.unpack('!IIIII',
wire[current : current + rdlen])
if not origin is None:
mname = mname.relativize(origin)
rname = rname.relativize(origin)
return cls(rdclass, rdtype, mname, rname,
five_ints[0], five_ints[1], five_ints[2], five_ints[3],
five_ints[4])
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.mname = self.mname.choose_relativity(origin, relativize)
self.rname = self.rname.choose_relativity(origin, relativize)
def _cmp(self, other):
v = cmp(self.mname, other.mname)
if v == 0:
v = cmp(self.rname, other.rname)
if v == 0:
self_ints = struct.pack('!IIIII', self.serial, self.refresh,
self.retry, self.expire, self.minimum)
other_ints = struct.pack('!IIIII', other.serial, other.refresh,
other.retry, other.expire,
other.minimum)
v = cmp(self_ints, other_ints)
return v

View file

@ -0,0 +1,78 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.rdata
import linkcheck.dns.rdatatype
class SSHFP(linkcheck.dns.rdata.Rdata):
"""SSHFP record
@ivar algorithm: the algorithm
@type algorithm: int
@ivar fp_type: the digest type
@type fp_type: int
@ivar fingerprint: the fingerprint
@type fingerprint: string
@see: draft-ietf-secsh-dns-05.txt"""
__slots__ = ['algorithm', 'fp_type', 'fingerprint']
def __init__(self, rdclass, rdtype, algorithm, fp_type,
fingerprint):
super(SSHFP, self).__init__(rdclass, rdtype)
self.algorithm = algorithm
self.fp_type = fp_type
self.fingerprint = fingerprint
def to_text(self, origin=None, relativize=True, **kw):
return '%d %d %s' % (self.algorithm,
self.fp_type,
linkcheck.dns.rdata._hexify(self.fingerprint,
chunksize=128))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
algorithm = tok.get_uint8()
fp_type = tok.get_uint8()
fingerprint = tok.get_string()
fingerprint = fingerprint.decode('hex_codec')
tok.get_eol()
return cls(rdclass, rdtype, algorithm, fp_type, fingerprint)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
header = struct.pack("!BB", self.algorithm, self.fp_type)
file.write(header)
file.write(self.fingerprint)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
header = struct.unpack("!BB", wire[current : current + 2])
current += 2
rdlen -= 2
fingerprint = wire[current : current + rdlen]
return cls(rdclass, rdtype, header[0], header[1], fingerprint)
from_wire = classmethod(from_wire)
def _cmp(self, other):
hs = struct.pack("!BB", self.algorithm, self.fp_type)
ho = struct.pack("!BB", other.algorithm, other.fp_type)
v = cmp(hs, ho)
if v == 0:
v = cmp(self.fingerprint, other.fingerprint)
return v

View file

@ -0,0 +1,86 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class TXT(linkcheck.dns.rdata.Rdata):
"""TXT record
@ivar strings: the text strings
@type strings: list of string
@see: RFC 1035"""
__slots__ = ['strings']
def __init__(self, rdclass, rdtype, strings):
super(TXT, self).__init__(rdclass, rdtype)
if isinstance(strings, str):
strings = [ strings ]
self.strings = strings[:]
def to_text(self, origin=None, relativize=True, **kw):
txt = ''
prefix = ''
for s in self.strings:
txt += '%s"%s"' % (prefix, linkcheck.dns.rdata._escapify(s))
prefix = ' '
return txt
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
strings = []
while 1:
(ttype, s) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
if ttype != linkcheck.dns.tokenizer.QUOTED_STRING:
raise linkcheck.dns.exception.SyntaxError, "expected a quoted string"
if len(s) > 255:
raise linkcheck.dns.exception.SyntaxError, "string too long"
strings.append(s)
if len(strings) == 0:
raise linkcheck.dns.exception.UnexpectedEnd
return cls(rdclass, rdtype, strings)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
for s in self.strings:
l = len(s)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(s)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
strings = []
while rdlen > 0:
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen:
raise linkcheck.dns.exception.FormError
s = wire[current : current + l]
current += l
rdlen -= l
strings.append(s)
return cls(rdclass, rdtype, strings)
from_wire = classmethod(from_wire)
def _cmp(self, other):
return cmp(self.strings, other.strings)

View file

@ -0,0 +1,63 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class X25(linkcheck.dns.rdata.Rdata):
"""X25 record
@ivar address: the PSDN address
@type address: string
@see: RFC 1183"""
__slots__ = ['address']
def __init__(self, rdclass, rdtype, address):
super(X25, self).__init__(rdclass, rdtype)
self.address = address
def to_text(self, origin=None, relativize=True, **kw):
return '"%s"' % linkcheck.dns.rdata._escapify(self.address)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
address = tok.get_string()
tok.get_eol()
return cls(rdclass, rdtype, address)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
l = len(self.address)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(self.address)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
l = ord(wire[current])
current += 1
rdlen -= 1
if l != rdlen:
raise linkcheck.dns.exception.FormError
address = wire[current : current + l]
return cls(rdclass, rdtype, address)
from_wire = classmethod(from_wire)
def _cmp(self, other):
return cmp(self.address, other.address)

View file

@ -0,0 +1,44 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Class ANY (generic) rdata type classes."""
__all__ = [
'AFSDB',
'CERT',
'CNAME',
'DNAME',
'DS',
'GPOS',
'HINFO',
'ISDN',
'KEY',
'LOC',
'MX',
'NS',
'NXT',
'PTR',
'RP',
'RT',
'SIG',
'SOA',
'TXT',
'X25',
'RRSIG',
'NSEC',
'DNSKEY',
'SSHFP',
]

View file

@ -0,0 +1,60 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.ipv4
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class A(linkcheck.dns.rdata.Rdata):
"""A record.
@ivar address: an IPv4 address
@type address: string (in the standard "dotted quad" format)"""
__slots__ = ['address']
def __init__(self, rdclass, rdtype, address):
super(A, self).__init__(rdclass, rdtype)
# check that it's OK
junk = linkcheck.dns.ipv4.inet_aton(address)
self.address = address
def to_text(self, origin=None, relativize=True, **kw):
return self.address
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
(ttype, address) = tok.get()
if ttype != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
t = tok.get_eol()
return cls(rdclass, rdtype, address)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
file.write(linkcheck.dns.ipv4.inet_aton(self.address))
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
address = linkcheck.dns.ipv4.inet_ntoa(wire[current : current + rdlen])
return cls(rdclass, rdtype, address)
from_wire = classmethod(from_wire)
def _cmp(self, other):
sa = linkcheck.dns.ipv4.inet_aton(self.address)
oa = linkcheck.dns.ipv4.inet_aton(other.address)
return cmp(sa, oa)

View file

@ -0,0 +1,61 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.inet
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class AAAA(linkcheck.dns.rdata.Rdata):
"""AAAA record.
@ivar address: an IPv6 address
@type address: string (in the standard IPv6 format)"""
__slots__ = ['address']
def __init__(self, rdclass, rdtype, address):
super(AAAA, self).__init__(rdclass, rdtype)
# check that it's OK
junk = linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET6, address)
self.address = address
def to_text(self, origin=None, relativize=True, **kw):
return self.address
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
(ttype, address) = tok.get()
if ttype != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
t = tok.get_eol()
return cls(rdclass, rdtype, address)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
file.write(linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET6, self.address))
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
address = linkcheck.dns.inet.inet_ntop(linkcheck.dns.inet.AF_INET6,
wire[current : current + rdlen])
return cls(rdclass, rdtype, address)
from_wire = classmethod(from_wire)
def _cmp(self, other):
sa = linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET6, self.address)
oa = linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET6, other.address)
return cmp(sa, oa)

View file

@ -0,0 +1,170 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO
import struct
import linkcheck.dns.exception
import linkcheck.dns.inet
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class APLItem(object):
"""An APL list item.
@ivar family: the address family (IANA address family registry)
@type family: int
@ivar negation: is this item negated?
@type negation: bool
@ivar address: the address
@type address: string
@ivar prefix: the prefix length
@type prefix: int
"""
__slots__ = ['family', 'negation', 'address', 'prefix']
def __init__(self, family, negation, address, prefix):
self.family = family
self.negation = negation
self.address = address
self.prefix = prefix
def __str__(self):
if self.negation:
return "!%d:%s/%s" % (self.family, self.address, self.prefix)
else:
return "%d:%s/%s" % (self.family, self.address, self.prefix)
def to_wire(self, file):
if self.family == 1:
address = linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET, self.address)
elif self.family == 2:
address = linkcheck.dns.inet.inet_pton(linkcheck.dns.inet.AF_INET6, self.address)
else:
address = self.address.decode('hex_codec')
#
# Truncate least significant zero bytes.
#
last = 0
for i in xrange(len(address) - 1, -1, -1):
if address[i] != chr(0):
last = i + 1
break
address = address[0 : last]
l = len(address)
assert l < 128
if self.negation:
l |= 0x80
header = struct.pack('!HBB', self.family, self.prefix, l)
file.write(header)
file.write(address)
class APL(linkcheck.dns.rdata.Rdata):
"""APL record.
@ivar items: a list of APL items
@type items: list of APL_Item
@see: RFC 3123"""
__slots__ = ['items']
def __init__(self, rdclass, rdtype, items):
super(APL, self).__init__(rdclass, rdtype)
self.items = items
def to_text(self, origin=None, relativize=True, **kw):
return ' '.join(map(lambda x: str(x), self.items))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
items = []
while 1:
(ttype, item) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
if item[0] == '!':
negation = True
item = item[1:]
else:
negation = False
(family, rest) = item.split(':', 1)
family = int(family)
(address, prefix) = rest.split('/', 1)
prefix = int(prefix)
item = APLItem(family, negation, address, prefix)
items.append(item)
return cls(rdclass, rdtype, items)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
for item in self.items:
item.to_wire(file)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
items = []
while 1:
if rdlen < 4:
raise linkcheck.dns.exception.FormError
header = struct.unpack('!HBB', wire[current : current + 4])
afdlen = header[2]
if afdlen > 127:
negation = True
afdlen -= 128
else:
negation = False
current += 4
rdlen -= 4
if rdlen < afdlen:
raise linkcheck.dns.exception.FormError
address = wire[current : current + afdlen]
l = len(address)
if header[0] == 1:
if l < 4:
address += '\x00' * (4 - l)
address = linkcheck.dns.inet.inet_ntop(linkcheck.dns.inet.AF_INET, address)
elif header[0] == 2:
if l < 16:
address += '\x00' * (16 - l)
address = linkcheck.dns.inet.inet_ntop(linkcheck.dns.inet.AF_INET6, address)
else:
#
# This isn't really right according to the RFC, but it
# seems better than throwing an exception
#
address = address.encode('hex_codec')
current += afdlen
rdlen -= afdlen
item = APLItem(header[0], negation, address, header[1])
items.append(item)
if rdlen == 0:
break
return cls(rdclass, rdtype, items)
from_wire = classmethod(from_wire)
def _cmp(self, other):
f = cStringIO.StringIO()
self.to_wire(f)
wire1 = f.getvalue()
f.seek(0)
f.truncate()
other.to_wire(f)
wire2 = f.getvalue()
f.close()
return cmp(wire1, wire2)

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.mxbase
class KX(linkcheck.dns.rdtypes.mxbase.UncompressedMX):
"""KX record"""
pass

View file

@ -0,0 +1,133 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.exception
import linkcheck.dns.name
import linkcheck.dns.rdata
def _write_string(file, s):
l = len(s)
assert l < 256
byte = chr(l)
file.write(byte)
file.write(s)
class NAPTR(linkcheck.dns.rdata.Rdata):
"""NAPTR record
@ivar order: order
@type order: int
@ivar preference: preference
@type preference: int
@ivar flags: flags
@type flags: string
@ivar service: service
@type service: string
@ivar regexp: regular expression
@type regexp: string
@ivar replacement: replacement name
@type replacement: linkcheck.dns.name.Name object
@see: RFC 3403"""
__slots__ = ['order', 'preference', 'flags', 'service', 'regexp',
'replacement']
def __init__(self, rdclass, rdtype, order, preference, flags, service,
regexp, replacement):
super(NAPTR, self).__init__(rdclass, rdtype)
self.order = order
self.preference = preference
self.flags = flags
self.service = service
self.regexp = regexp
self.replacement = replacement
def to_text(self, origin=None, relativize=True, **kw):
replacement = self.replacement.choose_relativity(origin, relativize)
return '%d %d "%s" "%s" "%s" %s' % \
(self.order, self.preference,
linkcheck.dns.rdata._escapify(self.flags),
linkcheck.dns.rdata._escapify(self.service),
linkcheck.dns.rdata._escapify(self.regexp),
self.replacement)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
order = tok.get_uint16()
preference = tok.get_uint16()
flags = tok.get_string()
service = tok.get_string()
regexp = tok.get_string()
replacement = tok.get_name()
replacement = replacement.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, order, preference, flags, service,
regexp, replacement)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
two_ints = struct.pack("!HH", self.order, self.preference)
file.write(two_ints)
_write_string(file, self.flags)
_write_string(file, self.service)
_write_string(file, self.regexp)
self.replacement.to_wire(file, compress, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(order, preference) = struct.unpack('!HH', wire[current : current + 4])
current += 4
rdlen -= 4
strings = []
for i in xrange(3):
l = ord(wire[current])
current += 1
rdlen -= 1
if l > rdlen or rdlen < 0:
raise linkcheck.dns.exception.FormError
s = wire[current : current + l]
current += l
rdlen -= l
strings.append(s)
(replacement, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
replacement = replacement.relativize(origin)
return cls(rdclass, rdtype, order, preference, strings[0], strings[1],
strings[2], replacement)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.replacement = self.replacement.choose_relativity(origin,
relativize)
def _cmp(self, other):
sp = struct.pack("!HH", self.order, self.preference)
op = struct.pack("!HH", other.order, self.preference)
v = cmp(sp, op)
if v == 0:
v = cmp(self.flags, other.flags)
if v == 0:
v = cmp(self.service, other.service)
if v == 0:
v = cmp(self.regexp, other.regexp)
if v == 0:
v = cmp(self.replacement, other.replacement)
return v

View file

@ -0,0 +1,60 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
class NSAP(linkcheck.dns.rdata.Rdata):
"""NSAP record.
@ivar address: a NASP
@type address: string
@see: RFC 1706"""
__slots__ = ['address']
def __init__(self, rdclass, rdtype, address):
super(NSAP, self).__init__(rdclass, rdtype)
self.address = address
def to_text(self, origin=None, relativize=True, **kw):
return "0x%s" % self.address.encode('hex_codec')
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
address = tok.get_string()
t = tok.get_eol()
if address[0:2] != '0x':
raise linkcheck.dns.exception.SyntaxError, 'string does not start with 0x'
address = address[2:].replace('.', '')
if len(address) % 2 != 0:
raise linkcheck.dns.exception.SyntaxError, 'hexstring has odd length'
address = address.decode('hex_codec')
return cls(rdclass, rdtype, address)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
file.write(self.address)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
address = wire[current : current + rdlen]
return cls(rdclass, rdtype, address)
from_wire = classmethod(from_wire)
def _cmp(self, other):
return cmp(self.address, other.address)

View file

@ -0,0 +1,21 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import linkcheck.dns.rdtypes.nsbase
class NSAP_PTR(linkcheck.dns.rdtypes.nsbase.UncompressedNS):
"""NSAP-PTR record"""
pass

View file

@ -0,0 +1,98 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class PX(linkcheck.dns.rdata.Rdata):
"""PX record.
@ivar preference: the preference value
@type preference: int
@ivar map822: the map822 name
@type map822: linkcheck.dns.name.Name object
@ivar mapx400: the mapx400 name
@type mapx400: linkcheck.dns.name.Name object
@see: RFC 2163"""
__slots__ = ['preference', 'map822', 'mapx400']
def __init__(self, rdclass, rdtype, preference, map822, mapx400):
super(PX, self).__init__(rdclass, rdtype)
self.preference = preference
self.map822 = map822
self.mapx400 = mapx400
def to_text(self, origin=None, relativize=True, **kw):
map822 = self.map822.choose_relativity(origin, relativize)
mapx400 = self.mapx400.choose_relativity(origin, relativize)
return '%d %s %s' % (self.preference, map822, mapx400)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
preference = tok.get_uint16()
map822 = tok.get_name()
map822 = map822.choose_relativity(origin, relativize)
mapx400 = tok.get_name(None)
mapx400 = mapx400.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, preference, map822, mapx400)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
pref = struct.pack("!H", self.preference)
file.write(pref)
self.map822.to_wire(file, None, origin)
self.mapx400.to_wire(file, None, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(preference, ) = struct.unpack('!H', wire[current : current + 2])
current += 2
rdlen -= 2
(map822, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused > rdlen:
raise linkcheck.dns.exception.FormError
current += cused
rdlen -= cused
if not origin is None:
map822 = map822.relativize(origin)
(mapx400, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
mapx400 = mapx400.relativize(origin)
return cls(rdclass, rdtype, preference, map822, mapx400)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.map822 = self.map822.choose_relativity(origin, relativize)
self.mapx400 = self.mapx400.choose_relativity(origin, relativize)
def _cmp(self, other):
sp = struct.pack("!H", self.preference)
op = struct.pack("!H", other.preference)
v = cmp(sp, op)
if v == 0:
v = cmp(self.map822, other.map822)
if v == 0:
v = cmp(self.mapx400, other.mapx400)
return v

View file

@ -0,0 +1,90 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class SRV(linkcheck.dns.rdata.Rdata):
"""SRV record
@ivar priority: the priority
@type priority: int
@ivar weight: the weight
@type weight: int
@ivar port: the port of the service
@type port: int
@ivar target: the target host
@type target: linkcheck.dns.name.Name object
@see: RFC 2782"""
__slots__ = ['priority', 'weight', 'port', 'target']
def __init__(self, rdclass, rdtype, priority, weight, port, target):
super(SRV, self).__init__(rdclass, rdtype)
self.priority = priority
self.weight = weight
self.port = port
self.target = target
def to_text(self, origin=None, relativize=True, **kw):
target = self.target.choose_relativity(origin, relativize)
return '%d %d %d %s' % (self.priority, self.weight, self.port,
target)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
priority = tok.get_uint16()
weight = tok.get_uint16()
port = tok.get_uint16()
target = tok.get_name(None)
target = target.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, priority, weight, port, target)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
three_ints = struct.pack("!HHH", self.priority, self.weight, self.port)
file.write(three_ints)
self.target.to_wire(file, compress, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(priority, weight, port) = struct.unpack('!HHH',
wire[current : current + 6])
current += 6
rdlen -= 6
(target, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
target = target.relativize(origin)
return cls(rdclass, rdtype, priority, weight, port, target)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.target = self.target.choose_relativity(origin, relativize)
def _cmp(self, other):
sp = struct.pack("!HHH", self.priority, self.weight, self.port)
op = struct.pack("!HHH", other.priority, self.weight, self.port)
v = cmp(sp, op)
if v == 0:
v = cmp(self.target, other.target)
return v

View file

@ -0,0 +1,114 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import socket
import struct
import linkcheck.dns.ipv4
import linkcheck.dns.rdata
_proto_tcp = socket.getprotobyname('tcp')
_proto_udp = socket.getprotobyname('udp')
class WKS(linkcheck.dns.rdata.Rdata):
"""WKS record
@ivar address: the address
@type address: string
@ivar protocol: the protocol
@type protocol: int
@ivar bitmap: the bitmap
@type bitmap: string
@see: RFC 1035"""
__slots__ = ['address', 'protocol', 'bitmap']
def __init__(self, rdclass, rdtype, address, protocol, bitmap):
super(WKS, self).__init__(rdclass, rdtype)
self.address = address
self.protocol = protocol
self.bitmap = bitmap
def to_text(self, origin=None, relativize=True, **kw):
bits = []
for i in xrange(0, len(self.bitmap)):
byte = ord(self.bitmap[i])
for j in xrange(0, 8):
if byte & (0x80 >> j):
bits.append(str(i * 8 + j))
text = ' '.join(bits)
return '%s %d %s' % (self.address, self.protocol, text)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
address = tok.get_string()
protocol = tok.get_string()
if protocol.isdigit():
protocol = int(protocol)
else:
protocol = socket.getprotobyname(protocol)
bitmap = []
while 1:
(ttype, value) = tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
if value.isdigit():
serv = int(value)
else:
if protocol != _proto_udp and protocol != _proto_tcp:
raise NotImplementedError, "protocol must be TCP or UDP"
if protocol == _proto_udp:
protocol_text = "udp"
else:
protocol_text = "tcp"
serv = socket.getservbyname(value, protocol_text)
i = serv // 8
l = len(bitmap)
if l < i + 1:
for j in xrange(l, i + 1):
bitmap.append('\x00')
bitmap[i] = chr(ord(bitmap[i]) | (0x80 >> (serv % 8)))
bitmap = linkcheck.dns.rdata._truncate_bitmap(bitmap)
return cls(rdclass, rdtype, address, protocol, bitmap)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
file.write(linkcheck.dns.ipv4.inet_aton(self.address))
protocol = struct.pack('!B', self.protocol)
file.write(protocol)
file.write(self.bitmap)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
address = linkcheck.dns.ipv4.inet_ntoa(wire[current : current + 4])
protocol, = struct.unpack('!B', wire[current + 4 : current + 5])
current += 5
rdlen -= 5
bitmap = wire[current : current + rdlen]
return cls(rdclass, rdtype, address, protocol, bitmap)
from_wire = classmethod(from_wire)
def _cmp(self, other):
sa = linkcheck.dns.ipv4.inet_aton(self.address)
oa = linkcheck.dns.ipv4.inet_aton(other.address)
v = cmp(sa, oa)
if v == 0:
sp = struct.pack('!B', self.protocol)
op = struct.pack('!B', other.protocol)
v = cmp(sp, op)
if v == 0:
v = cmp(self.bitmap, other.bitmap)
return v

View file

@ -0,0 +1,30 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Class IN rdata type classes."""
__all__ = [
'A',
'AAAA',
'APL',
'KX',
'PX',
'NAPTR',
'NSAP',
'NSAP_PTR',
'SRV',
'WKS',
]

View file

@ -0,0 +1,26 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS rdata type classes"""
__all__ = [
'ANY',
'IN',
'mxbase',
'nsbase',
'sigbase',
'keybase',
]

View file

@ -0,0 +1,151 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import linkcheck.dns.exception
import linkcheck.dns.dnssec
import linkcheck.dns.rdata
_flags_from_text = {
'NOCONF': (0x4000, 0xC000),
'NOAUTH': (0x8000, 0xC000),
'NOKEY': (0xC000, 0xC000),
'FLAG2': (0x2000, 0x2000),
'EXTEND': (0x1000, 0x1000),
'FLAG4': (0x0800, 0x0800),
'FLAG5': (0x0400, 0x0400),
'USER': (0x0000, 0x0300),
'ZONE': (0x0100, 0x0300),
'HOST': (0x0200, 0x0300),
'NTYP3': (0x0300, 0x0300),
'FLAG8': (0x0080, 0x0080),
'FLAG9': (0x0040, 0x0040),
'FLAG10': (0x0020, 0x0020),
'FLAG11': (0x0010, 0x0010),
'SIG0': (0x0000, 0x000f),
'SIG1': (0x0001, 0x000f),
'SIG2': (0x0002, 0x000f),
'SIG3': (0x0003, 0x000f),
'SIG4': (0x0004, 0x000f),
'SIG5': (0x0005, 0x000f),
'SIG6': (0x0006, 0x000f),
'SIG7': (0x0007, 0x000f),
'SIG8': (0x0008, 0x000f),
'SIG9': (0x0009, 0x000f),
'SIG10': (0x000a, 0x000f),
'SIG11': (0x000b, 0x000f),
'SIG12': (0x000c, 0x000f),
'SIG13': (0x000d, 0x000f),
'SIG14': (0x000e, 0x000f),
'SIG15': (0x000f, 0x000f),
}
_protocol_from_text = {
'NONE' : 0,
'TLS' : 1,
'EMAIL' : 2,
'DNSSEC' : 3,
'IPSEC' : 4,
'ALL' : 255,
}
class KEYBase(linkcheck.dns.rdata.Rdata):
"""KEY-like record base
@ivar flags: the key flags
@type flags: int
@ivar protocol: the protocol for which this key may be used
@type protocol: int
@ivar algorithm: the algorithm used for the key
@type algorithm: int
@ivar key: the public key
@type key: string"""
__slots__ = ['flags', 'protocol', 'algorithm', 'key']
def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key):
super(KEYBase, self).__init__(rdclass, rdtype)
self.flags = flags
self.protocol = protocol
self.algorithm = algorithm
self.key = key
def to_text(self, origin=None, relativize=True, **kw):
return '%d %d %d %s' % (self.flags, self.protocol, self.algorithm,
linkcheck.dns.rdata._base64ify(self.key))
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
flags = tok.get_string()
if flags.isdigit():
flags = int(flags)
else:
flag_names = flags.split('|')
flags = 0
for flag in flag_names:
v = _flags_from_text.get(flag)
if v is None:
raise linkcheck.dns.exception.SyntaxError, 'unknown flag %s' % flag
flags &= ~v[1]
flags |= v[0]
protocol = tok.get_string()
if protocol.isdigit():
protocol = int(protocol)
else:
protocol = _protocol_from_text.get(protocol)
if protocol is None:
raise linkcheck.dns.exception.SyntaxError, \
'unknown protocol %s' % protocol
algorithm = linkcheck.dns.dnssec.algorithm_from_text(tok.get_string())
chunks = []
while 1:
t = tok.get()
if t[0] == linkcheck.dns.tokenizer.EOL or t[0] == linkcheck.dns.tokenizer.EOF:
break
if t[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
chunks.append(t[1])
b64 = ''.join(chunks)
key = b64.decode('base64_codec')
return cls(rdclass, rdtype, flags, protocol, algorithm, key)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
header = struct.pack("!HBB", self.flags, self.protocol, self.algorithm)
file.write(header)
file.write(self.key)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
if rdlen < 4:
raise linkcheck.dns.exception.FormError
header = struct.unpack('!HBB', wire[current : current + 4])
current += 4
rdlen -= 4
key = wire[current : current + rdlen]
return cls(rdclass, rdtype, header[0], header[1], header[2],
key)
from_wire = classmethod(from_wire)
def _cmp(self, other):
hs = struct.pack("!HBB", self.flags, self.protocol, self.algorithm)
ho = struct.pack("!HBB", other.flags, other.protocol, other.algorithm)
v = cmp(hs, ho)
if v == 0:
v = cmp(self.key, other.key)
return v

View file

@ -0,0 +1,88 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""MX-like base classes."""
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class MXBase(linkcheck.dns.rdata.Rdata):
"""Base class for rdata that is like an MX record.
@ivar preference: the preference value
@type preference: int
@ivar exchange: the exchange name
@type exchange: linkcheck.dns.name.Name object"""
__slots__ = ['preference', 'exchange']
def __init__(self, rdclass, rdtype, preference, exchange):
super(MXBase, self).__init__(rdclass, rdtype)
self.preference = preference
self.exchange = exchange
def to_text(self, origin=None, relativize=True, **kw):
exchange = self.exchange.choose_relativity(origin, relativize)
return '%d %s' % (self.preference, exchange)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
preference = tok.get_uint16()
exchange = tok.get_name()
exchange = exchange.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, preference, exchange)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
pref = struct.pack("!H", self.preference)
file.write(pref)
self.exchange.to_wire(file, compress, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(preference, ) = struct.unpack('!H', wire[current : current + 2])
current += 2
rdlen -= 2
(exchange, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
exchange = exchange.relativize(origin)
return cls(rdclass, rdtype, preference, exchange)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.exchange = self.exchange.choose_relativity(origin, relativize)
def _cmp(self, other):
sp = struct.pack("!H", self.preference)
op = struct.pack("!H", other.preference)
v = cmp(sp, op)
if v == 0:
v = cmp(self.exchange, other.exchange)
return v
class UncompressedMX(MXBase):
"""Base class for rdata that is like an MX record, but whose name
is not compressed when convert to DNS wire format."""
def to_wire(self, file, compress = None, origin = None):
super(UncompressedMX, self).to_wire(file, None, origin)

View file

@ -0,0 +1,72 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""NS-like base classes."""
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.name
class NSBase(linkcheck.dns.rdata.Rdata):
"""Base class for rdata that is like an NS record.
@ivar target: the target name of the rdata
@type target: linkcheck.dns.name.Name object"""
__slots__ = ['target']
def __init__(self, rdclass, rdtype, target):
super(NSBase, self).__init__(rdclass, rdtype)
self.target = target
def to_text(self, origin=None, relativize=True, **kw):
target = self.target.choose_relativity(origin, relativize)
return str(target)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
target = tok.get_name()
target = target.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, target)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
self.target.to_wire(file, compress, origin)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
(target, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen],
current)
if cused != rdlen:
raise linkcheck.dns.exception.FormError
if not origin is None:
target = target.relativize(origin)
return cls(rdclass, rdtype, target)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.target = self.target.choose_relativity(origin, relativize)
def _cmp(self, other):
return cmp(self.target, other.target)
class UncompressedNS(NSBase):
"""Base class for rdata that is like an NS record, but whose name
is not compressed when convert to DNS wire format."""
def to_wire(self, file, compress = None, origin = None):
super(UncompressedNS, self).to_wire(file, None, origin)

View file

@ -0,0 +1,169 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import calendar
import struct
import time
import linkcheck.dns.dnssec
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.rdatatype
class BadSigTime(linkcheck.dns.exception.DNSException):
"""Raised when a SIG or RRSIG RR's time cannot be parsed."""
pass
def sigtime_to_posixtime(what):
if len(what) != 14:
raise BadSigTime
year = int(what[0:4])
month = int(what[4:6])
day = int(what[6:8])
hour = int(what[8:10])
minute = int(what[10:12])
second = int(what[12:14])
return calendar.timegm((year, month, day, hour, minute, second,
0, 0, 0))
def posixtime_to_sigtime(what):
return time.strftime('%Y%m%d%H%M%S', time.gmtime(what))
class SIGBase(linkcheck.dns.rdata.Rdata):
"""SIG-like record base
@ivar type_covered: the rdata type this signature covers
@type type_covered: int
@ivar algorithm: the algorithm used for the sig
@type algorithm: int
@ivar labels: number of labels
@type labels: int
@ivar original_ttl: the original TTL
@type original_ttl: long
@ivar expiration: signature expiration time
@type expiration: long
@ivar inception: signature inception time
@type inception: long
@ivar key_tag: the key tag
@type key_tag: int
@ivar signer: the signer
@type signer: linkcheck.dns.name.Name object
@ivar signature: the signature
@type signature: string"""
__slots__ = ['type_covered', 'algorithm', 'labels', 'original_ttl',
'expiration', 'inception', 'key_tag', 'signer',
'signature']
def __init__(self, rdclass, rdtype, type_covered, algorithm, labels,
original_ttl, expiration, inception, key_tag, signer,
signature):
super(SIGBase, self).__init__(rdclass, rdtype)
self.type_covered = type_covered
self.algorithm = algorithm
self.labels = labels
self.original_ttl = original_ttl
self.expiration = expiration
self.inception = inception
self.key_tag = key_tag
self.signer = signer
self.signature = signature
def covers(self):
return self.type_covered
def to_text(self, origin=None, relativize=True, **kw):
return '%s %d %d %d %s %s %d %s %s' % (
linkcheck.dns.rdatatype.to_text(self.type_covered),
self.algorithm,
self.labels,
self.original_ttl,
posixtime_to_sigtime(self.expiration),
posixtime_to_sigtime(self.inception),
self.key_tag,
self.signer,
linkcheck.dns.rdata._base64ify(self.signature)
)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
type_covered = linkcheck.dns.rdatatype.from_text(tok.get_string())
algorithm = linkcheck.dns.dnssec.algorithm_from_text(tok.get_string())
labels = tok.get_int()
original_ttl = tok.get_uint32()
expiration = sigtime_to_posixtime(tok.get_string())
inception = sigtime_to_posixtime(tok.get_string())
key_tag = tok.get_int()
signer = tok.get_name()
signer = signer.choose_relativity(origin, relativize)
chunks = []
while 1:
t = tok.get()
if t[0] == linkcheck.dns.tokenizer.EOL or t[0] == linkcheck.dns.tokenizer.EOF:
break
if t[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
chunks.append(t[1])
b64 = ''.join(chunks)
signature = b64.decode('base64_codec')
return cls(rdclass, rdtype, type_covered, algorithm, labels,
original_ttl, expiration, inception, key_tag, signer,
signature)
from_text = classmethod(from_text)
def to_wire(self, file, compress = None, origin = None):
header = struct.pack('!HBBIIIH', self.type_covered,
self.algorithm, self.labels,
self.original_ttl, self.expiration,
self.inception, self.key_tag)
file.write(header)
self.signer.to_wire(file, None, origin)
file.write(self.signature)
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
header = struct.unpack('!HBBIIIH', wire[current : current + 18])
current += 18
rdlen -= 18
(signer, cused) = linkcheck.dns.name.from_wire(wire[: current + rdlen], current)
current += cused
rdlen -= cused
if not origin is None:
signer = signer.relativize(origin)
signature = wire[current : current + rdlen]
return cls(rdclass, rdtype, header[0], header[1], header[2],
header[3], header[4], header[5], header[6], signer,
signature)
from_wire = classmethod(from_wire)
def choose_relativity(self, origin = None, relativize = True):
self.signer = self.signer.choose_relativity(origin, relativize)
def _cmp(self, other):
hs = struct.pack('!HBBIIIH', self.type_covered,
self.algorithm, self.labels,
self.original_ttl, self.expiration,
self.inception, self.key_tag)
ho = struct.pack('!HBBIIIH', other.type_covered,
other.algorithm, other.labels,
other.original_ttl, other.expiration,
other.inception, other.key_tag)
v = cmp(hs, ho)
if v == 0:
v = cmp(self.signer, other.signer)
if v == 0:
v = cmp(self.signature, other.signature)
return v

299
linkcheck/dns/renderer.py Normal file
View file

@ -0,0 +1,299 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Help for building DNS wire format messages"""
import cStringIO as StringIO
import random
import struct
import time
import linkcheck.dns.exception
import linkcheck.dns.tsig
QUESTION = 0
ANSWER = 1
AUTHORITY = 2
ADDITIONAL = 3
class Renderer(object):
"""Helper class for building DNS wire-format messages.
Most applications can use the higher-level L{linkcheck.dns.message.Message}
class and its to_wire() method to generate wire-format messages.
This class is for those applications which need finer control
over the generation of messages.
Typical use::
r = linkcheck.dns.renderer.Renderer(id=1, flags=0x80, max_size=512)
r.add_question(qname, qtype, qclass)
r.add_rrset(linkcheck.dns.renderer.ANSWER, rrset_1)
r.add_rrset(linkcheck.dns.renderer.ANSWER, rrset_2)
r.add_rrset(linkcheck.dns.renderer.AUTHORITY, ns_rrset)
r.add_edns(0, 0, 4096)
r.add_rrset(linkcheck.dns.renderer.ADDTIONAL, ad_rrset_1)
r.add_rrset(linkcheck.dns.renderer.ADDTIONAL, ad_rrset_2)
r.write_header()
r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac)
wire = r.get_wire()
@ivar output: where rendering is written
@type output: StringIO.StringIO object
@ivar id: the message id
@type id: int
@ivar flags: the message flags
@type flags: int
@ivar max_size: the maximum size of the message
@type max_size: int
@ivar origin: the origin to use when rendering relative names
@type origin: linkcheck.dns.name.Name object
@ivar compress: the compression table
@type compress: dict
@ivar section: the section currently being rendered
@type section: int (linkcheck.dns.renderer.QUESTION, linkcheck.dns.renderer.ANSWER,
linkcheck.dns.renderer.AUTHORITY, or linkcheck.dns.renderer.ADDITIONAL)
@ivar counts: list of the number of RRs in each section
@type counts: int list of length 4
@ivar mac: the MAC of the rendered message (if TSIG was used)
@type mac: string
"""
def __init__(self, id=None, flags=0, max_size=65535, origin=None):
"""Initialize a new renderer.
@param id: the message id
@type id: int
@param flags: the DNS message flags
@type flags: int
@param max_size: the maximum message size; the default is 65535.
If rendering results in a message greater than I{max_size},
then L{linkcheck.dns.exception.TooBig} will be raised.
@type max_size: int
@param origin: the origin to use when rendering relative names
@type origin: linkcheck.dns.name.Namem or None.
"""
self.output = StringIO.StringIO()
if id is None:
self.id = random.randint(0, 65535)
else:
self.id = id
self.flags = flags
self.max_size = max_size
self.origin = origin
self.compress = {}
self.section = QUESTION
self.counts = [0, 0, 0, 0]
self.output.write('\x00' * 12)
self.mac = ''
def _rollback(self, where):
"""Truncate the output buffer at offset I{where}, and remove any
compression table entries that pointed beyond the truncation
point.
@param where: the offset
@type where: int
"""
self.output.seek(where)
self.output.truncate()
keys_to_delete = []
for k, v in self.compress.iteritems():
if v >= where:
keys_to_delete.append(k)
for k in keys_to_delete:
del self.compress[k]
def _set_section(self, section):
"""Set the renderer's current section.
Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
ADDITIONAL. Sections may be empty.
@param section: the section
@type section: int
@raises linkcheck.dns.exception.FormError: an attempt was made to set
a section value less than the current section.
"""
if self.section != section:
if self.section > section:
raise linkcheck.dns.exception.FormError
self.section = section
def add_question(self, qname, rdtype, rdclass=linkcheck.dns.rdataclass.IN):
"""Add a question to the message.
@param qname: the question name
@type qname: linkcheck.dns.name.Name
@param rdtype: the question rdata type
@type rdtype: int
@param rdclass: the question rdata class
@type rdclass: int
"""
self._set_section(QUESTION)
before = self.output.tell()
qname.to_wire(self.output, self.compress, self.origin)
self.output.write(struct.pack("!HH", rdtype, rdclass))
after = self.output.tell()
if after >= self.max_size:
self._rollback(before)
raise linkcheck.dns.exception.TooBig
self.counts[QUESTION] += 1
def add_rrset(self, section, rrset, **kw):
"""Add the rrset to the specified section.
Any keyword arguments are passed on to the rdataset's to_wire()
routine.
@param section: the section
@type section: int
@param rrset: the rrset
@type rrset: linkcheck.dns.rrset.RRset object
"""
self._set_section(section)
before = self.output.tell()
n = rrset.to_wire(self.output, self.compress, self.origin, **kw)
after = self.output.tell()
if after >= self.max_size:
self._rollback(before)
raise linkcheck.dns.exception.TooBig
self.counts[section] += n
def add_rdataset(self, section, name, rdataset, **kw):
"""Add the rdataset to the specified section, using the specified
name as the owner name.
Any keyword arguments are passed on to the rdataset's to_wire()
routine.
@param section: the section
@type section: int
@param name: the owner name
@type name: linkcheck.dns.name.Name object
@param rdataset: the rdataset
@type rdataset: linkcheck.dns.rdataset.Rdataset object
"""
self._set_section(section)
before = self.output.tell()
n = rdataset.to_wire(name, self.output, self.compress, self.origin,
**kw)
after = self.output.tell()
if after >= self.max_size:
self._rollback(before)
raise linkcheck.dns.exception.TooBig
self.counts[section] += n
def add_edns(self, edns, ednsflags, payload):
"""Add an EDNS OPT record to the message.
@param edns: The EDNS level to use.
@type edns: int
@param ednsflags: EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle.
@type payload: int
@see: RFC 2671
"""
self._set_section(ADDITIONAL)
before = self.output.tell()
self.output.write(struct.pack('!BHHIH', 0, linkcheck.dns.rdatatype.OPT, payload,
ednsflags, 0))
after = self.output.tell()
if after >= self.max_size:
self._rollback(before)
raise linkcheck.dns.exception.TooBig
self.counts[ADDITIONAL] += 1
def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data,
request_mac):
"""Add a TSIG signature to the message.
@param keyname: the TSIG key name
@type keyname: linkcheck.dns.name.Name object
@param secret: the secret to use
@type secret: string
@param fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@param id: the message id to encode in the tsig signature
@type id: int
@param tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@param other_data: TSIG other data.
@type other_data: string
@param request_mac: This message is a response to the request which
had the specified MAC.
@type request_mac: string
"""
self._set_section(ADDITIONAL)
before = self.output.tell()
s = self.output.getvalue()
(tsig_rdata, self.mac, ctx) = linkcheck.dns.tsig.hmac_md5(s,
keyname,
secret,
int(time.time()),
fudge,
id,
tsig_error,
other_data,
request_mac)
keyname.to_wire(self.output, self.compress, self.origin)
self.output.write(struct.pack('!HHIH', linkcheck.dns.rdatatype.TSIG,
linkcheck.dns.rdataclass.ANY, 0, 0))
rdata_start = self.output.tell()
self.output.write(tsig_rdata)
after = self.output.tell()
assert after - rdata_start < 65536
if after >= self.max_size:
self._rollback(before)
raise linkcheck.dns.exception.TooBig
self.output.seek(rdata_start - 2)
self.output.write(struct.pack('!H', after - rdata_start))
self.counts[ADDITIONAL] += 1
self.output.seek(10)
self.output.write(struct.pack('!H', self.counts[ADDITIONAL]))
self.output.seek(0, 2)
def write_header(self):
"""Write the DNS message header.
Writing the DNS message header is done asfter all sections
have been rendered, but before the optional TSIG signature
is added.
"""
self.output.seek(0)
self.output.write(struct.pack('!HHHHHH', self.id, self.flags,
self.counts[0], self.counts[1],
self.counts[2], self.counts[3]))
self.output.seek(0, 2)
def get_wire(self):
"""Return the wire format message.
@rtype: string
"""
return self.output.getvalue()

640
linkcheck/dns/resolver.py Normal file
View file

@ -0,0 +1,640 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS stub resolver.
@var default_resolver: The default resolver object
@type default_resolver: linkcheck.dns.resolver.Resolver object"""
import socket
import sets
import sys
import time
import linkcheck.dns.exception
import linkcheck.dns.ifconfig
import linkcheck.dns.message
import linkcheck.dns.name
import linkcheck.dns.query
import linkcheck.dns.rcode
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
if sys.platform == 'win32':
import _winreg
class NXDOMAIN(linkcheck.dns.exception.DNSException):
"""The query name does not exist."""
pass
# The definition of the Timeout exception has moved from here to the
# linkcheck.dns.exception module. We keep linkcheck.dns.resolver.Timeout defined for
# backwards compatibility.
Timeout = linkcheck.dns.exception.Timeout
class NoAnswer(linkcheck.dns.exception.DNSException):
"""The response did not contain an answer to the question."""
pass
class NoNameservers(linkcheck.dns.exception.DNSException):
"""No non-broken nameservers are available to answer the query."""
pass
class Answer(object):
"""DNS stub resolver answer
Instances of this class bundle up the result of a successful DNS
resolution.
For convenience, the answer is iterable. "for a in answer" is
equivalent to "for a in answer.rrset".
Note that CNAMEs or DNAMEs in the response may mean that answer
node's name might not be the query name.
@ivar qname: The query name
@type qname: linkcheck.dns.name.Name object
@ivar rdtype: The query type
@type rdtype: int
@ivar rdclass: The query class
@type rdclass: int
@ivar response: The response message
@type response: linkcheck.dns.message.Message object
@ivar rrset: The answer
@type rrset: linkcheck.dns.rrset.RRset object
@ivar expiration: The time when the answer expires
@type expiration: float (seconds since the epoch)
"""
def __init__(self, qname, rdtype, rdclass, response):
self.qname = qname
self.rdtype = rdtype
self.rdclass = rdclass
self.response = response
min_ttl = -1
rrset = None
for count in xrange(0, 15):
try:
rrset = response.find_rrset(response.answer, qname,
rdclass, rdtype)
if min_ttl == -1 or rrset.ttl < min_ttl:
min_ttl = rrset.ttl
break
except KeyError:
if rdtype != linkcheck.dns.rdatatype.CNAME:
try:
crrset = response.find_rrset(response.answer,
qname,
rdclass,
linkcheck.dns.rdatatype.CNAME)
if min_ttl == -1 or crrset.ttl < min_ttl:
min_ttl = crrset.ttl
for rd in crrset:
qname = rd.target
break
continue
except KeyError:
raise NoAnswer("DNS response had no answer")
raise NoAnswer("DNS response had no answer")
if rrset is None:
raise NoAnswer("DNS response had no answer")
self.rrset = rrset
self.expiration = time.time() + min_ttl
def __getattr__(self, attr):
if attr == 'name':
return self.rrset.name
elif attr == 'ttl':
return self.rrset.ttl
elif attr == 'covers':
return self.rrset.covers
elif attr == 'rdclass':
return self.rrset.rdclass
elif attr == 'rdtype':
return self.rrset.rdtype
else:
raise AttributeError, attr
def __len__(self):
return len(self.rrset)
def __iter__(self):
return iter(self.rrset)
class Cache(object):
"""Simple DNS answer cache.
@ivar data: A dictionary of cached data
@type data: dict
@ivar cleaning_interval: The number of seconds between cleanings. The
default is 300 (5 minutes).
@type cleaning_interval: float
@ivar next_cleaning: The time the cache should next be cleaned (in seconds
since the epoch.)
@type next_cleaning: float
"""
def __init__(self, cleaning_interval=300.0):
"""Initialize a DNS cache.
@param cleaning_interval: the number of seconds between periodic
cleanings. The default is 300.0
@type cleaning_interval: float.
"""
self.data = {}
self.cleaning_interval = cleaning_interval
self.next_cleaning = time.time() + self.cleaning_interval
def maybe_clean(self):
"""Clean the cache if it's time to do so."""
now = time.time()
if self.next_cleaning <= now:
keys_to_delete = []
for (k, v) in self.data.iteritems():
if v.expiration <= now:
keys_to_delete.append(k)
for k in keys_to_delete:
del self.data[k]
now = time.time()
self.next_cleaning = now + self.cleaning_interval
def get(self, key):
"""Get the answer associated with I{key}. Returns None if
no answer is cached for the key.
@param key: the key
@type key: (linkcheck.dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@rtype: linkcheck.dns.resolver.Answer object or None
"""
self.maybe_clean()
v = self.data.get(key)
if v is None or v.expiration <= time.time():
return None
return v
def put(self, key, value):
"""Associate key and value in the cache.
@param key: the key
@type key: (linkcheck.dns.name.Name, int, int) tuple whose values are the
query name, rdtype, and rdclass.
@param value: The answer being cached
@type value: linkcheck.dns.resolver.Answer object
"""
self.maybe_clean()
self.data[key] = value
def flush(self, key=None):
"""Flush the cache.
If I{key} is specified, only that item is flushed. Otherwise
the entire cache is flushed.
@param key: the key to flush
@type key: (linkcheck.dns.name.Name, int, int) tuple or None
"""
if not key is None:
if self.data.has_key(key):
del self.data[key]
else:
self.data = {}
self.next_cleaning = time.time() + self.cleaning_interval
class Resolver(object):
"""DNS stub resolver
@ivar domain: The domain of this host
@type domain: linkcheck.dns.name.Name object
@ivar nameservers: A list of nameservers to query. Each nameserver is
a string which contains the IP address of a nameserver.
@type nameservers: list of strings
@ivar search: The search list. If the query name is a relative name,
the resolver will construct an absolute query name by appending the search
names one by one to the query name.
@type search: list of linkcheck.dns.name.Name objects
@ivar port: The port to which to send queries. The default is 53.
@type port: int
@ivar timeout: The number of seconds to wait for a response from a
server, before timing out.
@type timeout: float
@ivar lifetime: The total number of seconds to spend trying to get an
answer to the question. If the lifetime expires, a Timeout exception
will occur.
@type lifetime: float
@ivar keyring: The TSIG keyring to use. The default is None.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: linkcheck.dns.name.Name object
@ivar edns: The EDNS level to use. The default is -1, no Elinkcheck.dns.
@type edns: int
@ivar ednsflags: The EDNS flags
@type ednsflags: int
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
@ivar cache: The cache to use. The default is None.
@type cache: linkcheck.dns.resolver.Cache object
"""
def __init__(self, filename='/etc/resolv.conf', configure=True):
"""Initialize a resolver instance.
@param filename: The filename of a configuration file in
standard /etc/resolv.conf format. This parameter is meaningful
only when I{configure} is true and the platform is POSIX.
@type filename: string or file object
@param configure: If True (the default), the resolver instance
is configured in the normal fashion for the operating system
the resolver is running on. (I.e. a /etc/resolv.conf file on
POSIX systems and from the registry on Windows systems.)
@type configure: bool"""
self.reset()
if configure:
if sys.platform == 'win32':
self.read_registry()
elif filename:
self.read_resolv_conf(filename)
self.read_local_hosts()
def reset(self):
"""Reset all resolver configuration to the defaults."""
self.domain = \
linkcheck.dns.name.Name(linkcheck.dns.name.from_text(socket.gethostname())[1:])
if len(self.domain) == 0:
self.domain = linkcheck.dns.name.root
self.nameservers = []
self.localhosts = sets.Set([
'localhost',
'loopback',
'127.0.0.1',
'::1',
'ip6-localhost',
'ip6-loopback',
])
self.search = []
self.search_patters = ['www.%s.com', 'www.%s.org', 'www.%s.net', ]
self.port = 53
self.timeout = 2.0
self.lifetime = 30.0
self.keyring = None
self.keyname = None
self.edns = -1
self.ednsflags = 0
self.payload = 0
self.cache = None
def read_resolv_conf(self, f):
"""Process f as a file in the /etc/resolv.conf format. If f is
a string, it is used as the name of the file to open; otherwise it
is treated as the file itself."""
if isinstance(f, str) or isinstance(f, unicode):
f = open(f, 'r')
want_close = True
else:
want_close = False
try:
for l in f:
l = l.strip()
if len(l) == 0 or l[0] == '#' or l[0] == ';':
continue
tokens = l.split()
if len(tokens) < 2:
continue
if tokens[0] == 'nameserver':
self.nameservers.append(tokens[1])
elif tokens[0] == 'domain':
self.domain = linkcheck.dns.name.from_text(tokens[1])
elif tokens[0] == 'search':
for suffix in tokens[1:]:
self.search.append(linkcheck.dns.name.from_text(suffix))
finally:
if want_close:
f.close()
if len(self.nameservers) == 0:
self.nameservers.append('127.0.0.1')
def read_local_hosts (self):
# XXX is this default list of localhost stuff complete?
self.add_addrinfo(socket.gethostname())
# add system specific hosts for all enabled interfaces
for addr in self.read_local_ifaddrs():
self.add_addrinfo(addr)
def read_local_ifaddrs (self):
"""all active interfaces' ip addresses"""
ifc = linkcheck.dns.ifconfig.IfConfig()
return [ ifc.getAddr(iface) for iface in ifc.getInterfaceList()
if ifc.isUp(iface) ]
def add_addrinfo (self, host):
try:
addrinfo = socket.gethostbyaddr(host)
except socket.error:
self.localhosts.add(host.lower())
return
self.localhosts.add(addrinfo[0].lower())
for h in addrinfo[1]:
self.localhosts.add(h.lower())
for h in addrinfo[2]:
self.localhosts.add(h.lower())
def _config_win32_nameservers(self, nameservers, split_char=','):
"""Configure a NameServer registry entry."""
# we call str() on nameservers to convert it from unicode to ascii
ns_list = str(nameservers).split(split_char)
for ns in ns_list:
if not ns in self.nameservers:
self.nameservers.append(ns)
def _config_win32_domain(self, domain):
"""Configure a Domain registry entry."""
# we call str() on domain to convert it from unicode to ascii
self.domain = linkcheck.dns.name.from_text(str(domain))
def _config_win32_search(self, search):
"""Configure a Search registry entry."""
# we call str() on search to convert it from unicode to ascii
search_list = str(search).split(',')
for s in search_list:
if not s in self.search:
self.search.append(linkcheck.dns.name.from_text(s))
def _config_win32_add_ifaddr (self, key, name):
"""Add interface ip address to self.localhosts."""
try:
ip, rtype = _winreg.QueryValueEx(key, name)
if isinstance(ip, basestring) and ip:
self.localhosts.add(str(ip).lower())
except WindowsError:
pass
def _config_win32_fromkey(self, key):
"""Extract DNS info from a registry key."""
try:
enable_dhcp, rtype = _winreg.QueryValueEx(key, 'EnableDHCP')
except WindowsError:
enable_dhcp = False
if enable_dhcp:
try:
servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer')
except WindowsError:
servers = None
if servers:
# Annoyingly, the DhcpNameServer list is apparently space
# separated instead of comma separated like NameServer.
self._config_win32_nameservers(servers, ' ')
try:
dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain')
if dom:
self._config_win32_domain(servers)
except WindowsError:
pass
self._config_win32_add_ifaddr(key, 'DhcpIPAddress')
else:
try:
servers, rtype = _winreg.QueryValueEx(key, 'NameServer')
except WindowsError:
servers = None
if servers:
self._config_win32_nameservers(servers)
try:
dom, rtype = _winreg.QueryValueEx(key, 'Domain')
if dom:
self._config_win32_domain(servers)
except WindowsError:
pass
self._config_win32_add_ifaddr(key, 'IPAddress')
try:
search, rtype = _winreg.QueryValueEx(key, 'SearchList')
except WindowsError:
search = None
if search:
self._config_win32_search(servers)
def read_registry(self):
"""Extract resolver configuration from the Windows registry."""
lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
want_scan = False
try:
try:
# XP, 2000
tcp_params = _winreg.OpenKey(lm,
r'SYSTEM\CurrentControlSet'
r'\Services\Tcpip\Parameters')
want_scan = True
except EnvironmentError:
# ME
tcp_params = _winreg.OpenKey(lm,
r'SYSTEM\CurrentControlSet'
r'\Services\VxD\MSTCP')
try:
self._config_win32_fromkey(tcp_params)
finally:
tcp_params.Close()
if want_scan:
interfaces = _winreg.OpenKey(lm,
r'SYSTEM\CurrentControlSet'
r'\Services\Tcpip\Parameters'
r'\Interfaces')
try:
i = 0
while True:
try:
guid = _winreg.EnumKey(interfaces, i)
i += 1
key = _winreg.OpenKey(interfaces, guid)
try:
# enabled interfaces seem to have a non-empty
# NTEContextList
try:
(nte, ttype) = _winreg.QueryValueEx(key,
'NTEContextList')
except WindowsError:
nte = None
if nte:
self._config_win32_fromkey(key)
finally:
key.Close()
except EnvironmentError:
break
finally:
interfaces.Close()
finally:
lm.Close()
def query(self, qname, rdtype=linkcheck.dns.rdatatype.A,
rdclass=linkcheck.dns.rdataclass.IN, tcp=False):
"""Query nameservers to find the answer to the question.
The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects
of the appropriate type, or strings that can be converted into objects
of the appropriate type. E.g. For I{rdtype} the integer 2 and the
the string 'NS' both mean to query for records with DNS rdata type NS.
@param qname: the query name
@type qname: linkcheck.dns.name.Name object or string
@param rdtype: the query type
@type rdtype: int or string
@param rdclass: the query class
@type rdclass: int or string
@param tcp: use TCP to make the query (default is False).
@type tcp: bool
@rtype: linkcheck.dns.resolver.Answer instance
@raises Timeout: no answers could be found in the specified lifetime
@raises NXDOMAIN: the query name does not exist
@raises NoAnswer: the response did not contain an answer
@raises NoNameservers: no non-broken nameservers are available to
answer the question."""
if isinstance(qname, str):
qname = linkcheck.dns.name.from_text(qname, None)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(rdclass, str):
rdclass = linkcheck.dns.rdataclass.from_text(rdclass)
qnames_to_try = []
if qname.is_absolute():
qnames_to_try.append(qname)
else:
if len(qname) > 1:
qnames_to_try.append(qname.concatenate(linkcheck.dns.name.root))
if self.search:
for suffix in self.search:
qnames_to_try.append(qname.concatenate(suffix))
else:
qnames_to_try.append(qname.concatenate(self.domain))
all_nxdomain = True
start = time.time()
for qname in qnames_to_try:
if self.cache:
answer = self.cache.get((qname, rdtype, rdclass))
if answer:
return answer
request = linkcheck.dns.message.make_query(qname, rdtype, rdclass)
if not self.keyname is None:
request.use_tsig(self.keyring, self.keyname)
request.use_edns(self.edns, self.ednsflags, self.payload)
response = None
#
# make a copy of the servers list so we can alter it later.
#
nameservers = self.nameservers[:]
while response is None:
if len(nameservers) == 0:
raise NoNameservers("No DNS servers could answer the query")
for nameserver in nameservers:
now = time.time()
if now < start:
# Time going backwards is bad. Just give up.
raise Timeout("DNS query timed out")
duration = now - start
if duration >= self.lifetime:
raise Timeout("DNS query timed out")
timeout = min(self.lifetime - duration, self.timeout)
try:
if tcp:
response = linkcheck.dns.query.tcp(request, nameserver,
timeout, self.port)
else:
response = linkcheck.dns.query.udp(request, nameserver,
timeout, self.port)
except (socket.error, dns.exception.Timeout):
#
# Communication failure or timeout. Go to the
# next server
#
response = None
continue
except linkcheck.dns.query.UnexpectedSource:
#
# Who knows? Keep going.
#
response = None
continue
except linkcheck.dns.exception.FormError:
#
# We don't understand what this server is
# saying. Take it out of the mix and
# continue.
#
nameservers.remove(nameserver)
response = None
continue
rcode = response.rcode()
if rcode == linkcheck.dns.rcode.NOERROR or \
rcode == linkcheck.dns.rcode.NXDOMAIN:
break
response = None
if response.rcode() == linkcheck.dns.rcode.NXDOMAIN:
continue
all_nxdomain = False
break
if all_nxdomain:
raise NXDOMAIN("Domain does not exist")
answer = Answer(qname, rdtype, rdclass, response)
if self.cache:
self.cache.put((qname, rdtype, rdclass), answer)
return answer
def use_tsig(self, keyring, keyname=None):
"""Add a TSIG signature to the query.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key."""
self.keyring = keyring
if keyname is None:
self.keyname = self.keyring.keys()[0]
else:
self.keyname = keyname
def use_edns(self, edns, ednsflags, payload):
"""Configure Elinkcheck.dns.
@param edns: The EDNS level to use. The default is -1, no Elinkcheck.dns.
@type edns: int
@param ednsflags: The EDNS flags
@type ednsflags: int
@param payload: The EDNS payload size. The default is 0.
@type payload: int"""
if edns is None:
edns = -1
self.edns = edns
self.ednsflags = ednsflags
self.payload = payload
default_resolver = None
def query(qname, rdtype=linkcheck.dns.rdatatype.A, rdclass=linkcheck.dns.rdataclass.IN,
tcp=False):
"""Query nameservers to find the answer to the question.
This is a convenience function that uses the default resolver
object to make the query.
@see: L{linkcheck.dns.resolver.Resolver.query} for more information on the
parameters."""
global default_resolver
if default_resolver is None:
default_resolver = Resolver()
return default_resolver.query(qname, rdtype, rdclass, tcp)

169
linkcheck/dns/rrset.py Normal file
View file

@ -0,0 +1,169 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS RRsets (an RRset is a named rdataset)"""
import linkcheck.dns.name
import linkcheck.dns.rdataset
import linkcheck.dns.rdataclass
import linkcheck.dns.renderer
class RRset(linkcheck.dns.rdataset.Rdataset):
"""A DNS RRset (named rdataset).
RRset inherits from Rdataset, and RRsets can be treated as
Rdatasets in most cases. There are, however, a few notable
exceptions. RRsets have different to_wire() and to_text() method
arguments, reflecting the fact that RRsets always have an owner
name.
"""
__slots__ = ['name', 'deleting']
def __init__(self, name, rdclass, rdtype, covers=linkcheck.dns.rdatatype.NONE,
deleting=None):
"""Create a new RRset."""
super(RRset, self).__init__(rdclass, rdtype)
self.name = name
self.deleting = deleting
def _clone(self):
obj = super(RRset, self)._clone()
obj.name = self.name
obj.deleting = self.deleting
return obj
def __repr__(self):
if self.covers == 0:
ctext = ''
else:
ctext = '(' + linkcheck.dns.rdatatype.to_text(self.covers) + ')'
if not self.deleting is None:
dtext = ' delete=' + linkcheck.dns.rdataclass.to_text(self.deleting)
else:
dtext = ''
return '<DNS ' + str(self.name) + ' ' + \
linkcheck.dns.rdataclass.to_text(self.rdclass) + ' ' + \
linkcheck.dns.rdatatype.to_text(self.rdtype) + ctext + dtext + ' RRset>'
def __str__(self):
return self.to_text()
def __eq__(self, other):
"""Two RRsets are equal if they have the same name and the same
rdataset
@rtype: bool"""
if not isinstance(other, RRset):
return False
if self.name != other.name:
return False
return super(RRset, self).__eq__(other)
def match(self, name, rdclass, rdtype, covers, deleting=None):
"""Returns True if this rrset matches the specified class, type,
covers, and deletion state."""
if not super(RRset, self).match(rdclass, rdtype, covers):
return False
if self.name != name or self.deleting != deleting:
return False
return True
def to_text(self, origin=None, relativize=True, **kw):
"""Convert the RRset into DNS master file format.
@see: L{linkcheck.dns.name.Name.choose_relativity} for more information
on how I{origin} and I{relativize} determine the way names
are emitted.
Any additional keyword arguments are passed on to the rdata
to_text() method.
@param origin: The origin for relative names, or None.
@type origin: linkcheck.dns.name.Name object
@param relativize: True if names should names be relativized
@type relativize: bool"""
return super(RRset, self).to_text(self.name, origin, relativize,
self.deleting, **kw)
def to_wire(self, file, compress=None, origin=None, **kw):
"""Convert the RRset to wire format."""
return super(RRset, self).to_wire(self.name, file, compress, origin,
self.deleting, **kw)
def from_text_list(name, ttl, rdclass, rdtype, text_rdatas):
"""Create an RRset with the specified name, TTL, class, and type, and with
the specified list of rdatas in text format.
@rtype: linkcheck.dns.rrset.RRset object
"""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if isinstance(rdclass, str):
rdclass = linkcheck.dns.rdataclass.from_text(rdclass)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
r = RRset(name, rdclass, rdtype)
r.update_ttl(ttl)
for t in text_rdatas:
rd = linkcheck.dns.rdata.from_text(r.rdclass, r.rdtype, t)
r.add(rd)
return r
def from_text(name, ttl, rdclass, rdtype, *text_rdatas):
"""Create an RRset with the specified name, TTL, class, and type and with
the specified rdatas in text format.
@rtype: linkcheck.dns.rrset.RRset object
"""
return from_text_list(name, ttl, rdclass, rdtype, text_rdatas)
def from_rdata_list(name, ttl, rdatas):
"""Create an RRset with the specified name and TTL, and with
the specified list of rdata objects.
@rtype: linkcheck.dns.rrset.RRset object
"""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if len(rdatas) == 0:
raise ValueError, "rdata list must not be empty"
r = None
for rd in rdatas:
if r is None:
r = RRset(name, rd.rdclass, rd.rdtype)
r.update_ttl(ttl)
first_time = False
r.add(rd)
return r
def from_rdata(name, ttl, *rdatas):
"""Create an RRset with the specified name and TTL, and with
the specified rdata objects.
@rtype: linkcheck.dns.rrset.RRset object
"""
return from_rdata_list(name, ttl, rdatas)

251
linkcheck/dns/set.py Normal file
View file

@ -0,0 +1,251 @@
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""A simple Set class."""
class Set(object):
"""A simple set class.
Sets are not in Python until 2.3, and rdata are not immutable so
we cannot use sets.Set anyway. This class implements subset of
the 2.3 Set interface using a list as the container.
@ivar items: A list of the items which are in the set
@type items: list"""
__slots__ = ['items']
def __init__(self, items=None):
"""Initialize the set.
@param items: the initial set of items
@type items: any iterable or None
"""
self.items = []
if not items is None:
for item in items:
self.add(item)
def __repr__(self):
return "dns.simpleset.Set(%s)" % repr(self.items)
def add(self, item):
"""Add an item to the set."""
if not item in self.items:
self.items.append(item)
def remove(self, item):
"""Remove an item from the set."""
self.items.remove(item)
def discard(self, item):
"""Remove an item from the set if present."""
try:
self.items.remove(item)
except ValueError:
pass
def _clone(self):
"""Make a (shallow) copy of the set.
There is a 'clone protocol' that subclasses of this class
should use. To make a copy, first call your super's _clone()
method, and use the object returned as the new instance. Then
make shallow copies of the attributes defined in the subclass.
This protocol allows us to write the set algorithms that
return new instances (e.g. union) once, and keep using them in
subclasses.
"""
cls = self.__class__
obj = cls.__new__(cls)
obj.items = list(self.items)
return obj
def __copy__(self):
"""Make a (shallow) copy of the set."""
return self._clone()
def copy(self):
"""Make a (shallow) copy of the set."""
return self._clone()
def union_update(self, other):
"""Update the set, adding any elements from other which are not
already in the set.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError, 'other must be a Set instance'
if self is other:
return
for item in other.items:
self.add(item)
def intersection_update(self, other):
"""Update the set, removing any elements from other which are not
in both sets.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError, 'other must be a Set instance'
if self is other:
return
# we make a copy of the list so that we can remove items from
# the list without breaking the iterator.
for item in list(self.items):
if item not in other.items:
self.items.remove(item)
def difference_update(self, other):
"""Update the set, removing any elements from other which are in
the set.
@param other: the collection of items with which to update the set
@type other: Set object
"""
if not isinstance(other, Set):
raise ValueError, 'other must be a Set instance'
if self is other:
self.items = []
else:
for item in other.items:
self.discard(item)
def union(self, other):
"""Return a new set which is the union of I{self} and I{other}.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
"""
obj = self._clone()
obj.union_update(other)
return obj
def intersection(self, other):
"""Return a new set which is the intersection of I{self} and I{other}.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
"""
obj = self._clone()
obj.intersection_update(other)
return obj
def difference(self, other):
"""Return a new set which I{self} - I{other}, i.e. the items
in I{self} which are not also in I{other}.
@param other: the other set
@type other: Set object
@rtype: the same type as I{self}
"""
obj = self._clone()
obj.difference_update(other)
return obj
def __or__(self, other):
return self.union(other)
def __and__(self, other):
return self.intersection(other)
def __add__(self, other):
return self.union(other)
def __sub__(self, other):
return self.difference(other)
def __ior__(self, other):
self.union_update(other)
return self
def __iand__(self, other):
self.intersection_update(other)
return self
def __iadd__(self, other):
self.union_update(other)
return self
def __isub__(self, other):
self.difference_update(other)
return self
def update(self, other):
"""Update the set, adding any elements from other which are not
already in the set.
@param other: the collection of items with which to update the set
@type other: any iterable type"""
for item in other:
self.add(item)
def clear(self):
"""Make the set empty."""
self.items = []
def __eq__(self, other):
# Yes, this is inefficient but the sets we're dealing with are
# usually quite small, so it shouldn't hurt too much.
for item in self.items:
if not item in other.items:
return False
for item in other.items:
if not item in self.items:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __len__(self):
return len(self.items)
def __iter__(self):
return iter(self.items)
def issubset(self, other):
"""Is I{self} a subset of I{other}?
@rtype: bool
"""
if not isinstance(other, Set):
raise ValueError, 'other must be a Set instance'
for item in self.items:
if not item in other.items:
return False
return True
def issuperset(self, other):
"""Is I{self} a superset of I{other}?
@rtype: bool
"""
if not isinstance(other, Set):
raise ValueError, 'other must be a Set instance'
for item in other.items:
if not item in self.items:
return False
return True

View file

@ -0,0 +1 @@
# -*- coding: iso-8859-1 -*-

204
linkcheck/dns/tests/example Normal file
View file

@ -0,0 +1,204 @@
; Copyright (C) 2000, 2001 Internet Software Consortium.
;
; Permission to use, copy, modify, and distribute this software for any
; purpose with or without fee is hereby granted, provided that the above
; copyright notice and this permission notice appear in all copies.
;
; THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
; INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
; FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
; WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
$ORIGIN .
$TTL 300 ; 5 minutes
example IN SOA ns1.example. hostmaster.example. (
1 ; serial
2000 ; refresh (2000 seconds)
2000 ; retry (2000 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
example. NS ns1.example.
ns1.example. A 10.53.0.1
example. NS ns2.example.
ns2.example. A 10.53.0.2
$ORIGIN example.
* MX 10 mail
a TXT "foo foo foo"
PTR foo.net.
;; The next line not starting with ';;' is leading whitespace followed by
;; EOL. We want to treat that as if EOL had appeared alone.
;; The next line not starting with ';;' is leading whitespace followed by
;; a comment followed by EOL. We want to treat that as if EOL had appeared
;; alone.
; foo
$TTL 3600 ; 1 hour
a01 A 0.0.0.0
a02 A 255.255.255.255
;;
;; XXXRTH dnspython doesn't currently implement A6, and since
;; A6 records are effectively dead, it may never do so.
;;
;;a601 A6 0 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
;; A6 64 ::ffff:ffff:ffff:ffff foo.
;; A6 127 ::1 foo.
;; A6 128 .
aaaa01 AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
aaaa02 AAAA ::1
afsdb01 AFSDB 0 hostname
afsdb02 AFSDB 65535 .
$TTL 300 ; 5 minutes
b CNAME foo.net.
c A 73.80.65.49
$TTL 3600 ; 1 hour
cert01 CERT 65534 65535 PRIVATEOID (
MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi
WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl
d80jEeC8aTrO+KKmCaY= )
cname01 CNAME cname-target.
cname02 CNAME cname-target
cname03 CNAME .
$TTL 300 ; 5 minutes
d A 73.80.65.49
$TTL 3600 ; 1 hour
dname01 DNAME dname-target.
dname02 DNAME dname-target
dname03 DNAME .
$TTL 300 ; 5 minutes
e MX 10 mail
TXT "one"
TXT "three"
TXT "two"
A 73.80.65.49
A 73.80.65.50
A 73.80.65.52
A 73.80.65.51
f A 73.80.65.52
$TTL 3600 ; 1 hour
gpos01 GPOS "-22.6882" "116.8652" "250.0"
;;
;; XXXRTH I have commented out the following line because I don't think
;; it is a valid GPOS record.
;;
;;gpos02 GPOS "" "" ""
hinfo01 HINFO "Generic PC clone" "NetBSD-1.4"
hinfo02 HINFO "PC" "NetBSD"
isdn01 ISDN "isdn-address"
isdn02 ISDN "isdn-address" "subaddress"
isdn03 ISDN "isdn-address"
isdn04 ISDN "isdn-address" "subaddress"
key01 KEY 512 255 1 (
AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR
yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3
GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o
jqf0BaqHT+8= )
key02 KEY HOST|FLAG4 DNSSEC RSAMD5 (
AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR
yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3
GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o
jqf0BaqHT+8= )
kx01 KX 10 kdc
kx02 KX 10 .
loc01 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m
loc02 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m
loc03 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
loc04 LOC 60 9 1.5 N 24 39 0.000 E 10.00m 20m 2000m 20m
loc05 LOC 60 9 1.51 N 24 39 0.000 E 10.00m 20m 2000m 20m
;;
;; XXXRTH These are all obsolete and unused. dnspython doesn't implement
;; them
;;mb01 MG madname
;;mb02 MG .
;;mg01 MG mgmname
;;mg02 MG .
;;minfo01 MINFO rmailbx emailbx
;;minfo02 MINFO . .
;;mr01 MR mrname
;;mr02 MR .
mx01 MX 10 mail
mx02 MX 10 .
naptr01 NAPTR 0 0 "" "" "" .
naptr02 NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo.
nsap-ptr01 NSAP-PTR foo.
NSAP-PTR .
nsap01 NSAP 0x47000580005a0000000001e133ffffff00016100
nsap02 NSAP 0x47.000580005a0000000001e133ffffff000161.00
nxt01 NXT a.secure ( NS SOA MX SIG KEY LOC NXT )
nxt02 NXT . ( NSAP-PTR NXT )
nxt03 NXT . ( A )
nxt04 NXT . ( 127 )
ptr01 PTR example.
px01 PX 65535 foo. bar.
px02 PX 65535 . .
rp01 RP mbox-dname txt-dname
rp02 RP . .
rt01 RT 0 intermediate-host
rt02 RT 65535 .
$TTL 300 ; 5 minutes
s NS ns.s
$ORIGIN s.example.
ns A 73.80.65.49
$ORIGIN example.
$TTL 3600 ; 1 hour
sig01 SIG NXT 1 3 3600 (
20200101000000 20030101000000 2143 foo
MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi
WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl
d80jEeC8aTrO+KKmCaY= )
srv01 SRV 0 0 0 .
srv02 SRV 65535 65535 65535 old-slow-box.example.com.
$TTL 301 ; 5 minutes 1 second
t A 73.80.65.49
$TTL 3600 ; 1 hour
txt01 TXT "foo"
txt02 TXT "foo" "bar"
txt03 TXT "foo"
txt04 TXT "foo" "bar"
txt05 TXT "foo bar"
txt06 TXT "foo bar"
txt07 TXT "foo bar"
txt08 TXT "foo\010bar"
txt09 TXT "foo\010bar"
txt10 TXT "foo bar"
txt11 TXT "\"foo\""
txt12 TXT "\"foo\""
$TTL 300 ; 5 minutes
u TXT "txt-not-in-nxt"
$ORIGIN u.example.
a A 73.80.65.49
b A 73.80.65.49
$ORIGIN example.
$TTL 3600 ; 1 hour
wks01 WKS 10.0.0.1 6 ( 0 1 2 21 23 )
wks02 WKS 10.0.0.1 17 ( 0 1 2 53 )
wks03 WKS 10.0.0.2 6 ( 65535 )
x2501 X25 "123456789"
ds01 DS 12345 3 1 123456789abcdef67890123456789abcdef67890
apl01 APL 1:192.168.32.0/21 !1:192.168.38.0/28
apl02 APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
unknown2 TYPE999 \# 8 0a0000010a000001
rrsig01 RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/ vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11 U6Nld80jEeC8aTrO+KKmCaY=
nsec01 NSEC a.secure. A MX RRSIG NSEC TYPE1234
nsec02 NSEC . NSAP-PTR NSEC
nsec03 NSEC . NSEC TYPE65535
dnskey01 DNSKEY 512 255 1 (
AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR
yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3
GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o
jqf0BaqHT+8= )
dnskey02 DNSKEY HOST|FLAG4 DNSSEC RSAMD5 (
AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR
yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3
GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o
jqf0BaqHT+8= )
;
; test known type using unknown RR syntax
;
unknown3 A \# 4 7f000002
sshfp1 SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab

View file

@ -0,0 +1,105 @@
@ 300 IN SOA ns1 hostmaster 1 2000 2000 1814400 3600
@ 300 IN NS ns1
@ 300 IN NS ns2
* 300 IN MX 10 mail
a 300 IN TXT "foo foo foo"
a 300 IN PTR foo.net.
a01 3600 IN A 0.0.0.0
a02 3600 IN A 255.255.255.255
aaaa01 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
aaaa02 3600 IN AAAA ::1
afsdb01 3600 IN AFSDB 0 hostname
afsdb02 3600 IN AFSDB 65535 .
apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
apl02 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
b 300 IN CNAME foo.net.
c 300 IN A 73.80.65.49
cert01 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
cname01 3600 IN CNAME cname-target.
cname02 3600 IN CNAME cname-target
cname03 3600 IN CNAME .
d 300 IN A 73.80.65.49
dname01 3600 IN DNAME dname-target.
dname02 3600 IN DNAME dname-target
dname03 3600 IN DNAME .
dnskey01 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
dnskey02 3600 IN DNSKEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
ds01 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890
e 300 IN MX 10 mail
e 300 IN TXT "one"
e 300 IN TXT "three"
e 300 IN TXT "two"
e 300 IN A 73.80.65.49
e 300 IN A 73.80.65.50
e 300 IN A 73.80.65.52
e 300 IN A 73.80.65.51
f 300 IN A 73.80.65.52
gpos01 3600 IN GPOS -22.6882 116.8652 250.0
hinfo01 3600 IN HINFO "Generic PC clone" "NetBSD-1.4"
hinfo02 3600 IN HINFO "PC" "NetBSD"
isdn01 3600 IN ISDN "isdn-address"
isdn02 3600 IN ISDN "isdn-address" "subaddress"
isdn03 3600 IN ISDN "isdn-address"
isdn04 3600 IN ISDN "isdn-address" "subaddress"
key01 3600 IN KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
key02 3600 IN KEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
kx01 3600 IN KX 10 kdc
kx02 3600 IN KX 10 .
loc01 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc02 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc03 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m
loc04 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc05 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
mx01 3600 IN MX 10 mail
mx02 3600 IN MX 10 .
naptr01 3600 IN NAPTR 0 0 "" "" "" .
naptr02 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo.
ns1 300 IN A 10.53.0.1
ns2 300 IN A 10.53.0.2
nsap-ptr01 3600 IN NSAP-PTR foo.
nsap-ptr01 3600 IN NSAP-PTR .
nsap01 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100
nsap02 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100
nsec01 3600 IN NSEC a.secure. TYPE1234
nsec02 3600 IN NSEC . NSAP-PTR NSEC
nsec03 3600 IN NSEC . TYPE65535
nxt01 3600 IN NXT a.secure NS SOA MX SIG KEY LOC NXT
nxt02 3600 IN NXT . NSAP-PTR NXT
nxt03 3600 IN NXT . A
nxt04 3600 IN NXT . TYPE127
ptr01 3600 IN PTR @
px01 3600 IN PX 65535 foo. bar.
px02 3600 IN PX 65535 . .
rp01 3600 IN RP mbox-dname txt-dname
rp02 3600 IN RP . .
rrsig01 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
rt01 3600 IN RT 0 intermediate-host
rt02 3600 IN RT 65535 .
s 300 IN NS ns.s
ns.s 300 IN A 73.80.65.49
sig01 3600 IN SIG NXT 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
srv01 3600 IN SRV 0 0 0 .
srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
t 301 IN A 73.80.65.49
txt01 3600 IN TXT "foo"
txt02 3600 IN TXT "foo" "bar"
txt03 3600 IN TXT "foo"
txt04 3600 IN TXT "foo" "bar"
txt05 3600 IN TXT "foo bar"
txt06 3600 IN TXT "foo bar"
txt07 3600 IN TXT "foo bar"
txt08 3600 IN TXT "foo\010bar"
txt09 3600 IN TXT "foo\010bar"
txt10 3600 IN TXT "foo bar"
txt11 3600 IN TXT "\"foo\""
txt12 3600 IN TXT "\"foo\""
u 300 IN TXT "txt-not-in-nxt"
a.u 300 IN A 73.80.65.49
b.u 300 IN A 73.80.65.49
unknown2 3600 IN TYPE999 \# 8 0a0000010a000001
unknown3 3600 IN A 127.0.0.2
wks01 3600 IN WKS 10.0.0.1 6 0 1 2 21 23
wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53
wks03 3600 IN WKS 10.0.0.2 6 65535
x2501 3600 IN X25 "123456789"

View file

@ -0,0 +1,105 @@
example. 300 IN SOA ns1.example. hostmaster.example. 1 2000 2000 1814400 3600
example. 300 IN NS ns1.example.
example. 300 IN NS ns2.example.
*.example. 300 IN MX 10 mail.example.
a.example. 300 IN TXT "foo foo foo"
a.example. 300 IN PTR foo.net.
a01.example. 3600 IN A 0.0.0.0
a02.example. 3600 IN A 255.255.255.255
aaaa01.example. 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
aaaa02.example. 3600 IN AAAA ::1
afsdb01.example. 3600 IN AFSDB 0 hostname.example.
afsdb02.example. 3600 IN AFSDB 65535 .
apl01.example. 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
apl02.example. 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
b.example. 300 IN CNAME foo.net.
c.example. 300 IN A 73.80.65.49
cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
cname01.example. 3600 IN CNAME cname-target.
cname02.example. 3600 IN CNAME cname-target.example.
cname03.example. 3600 IN CNAME .
d.example. 300 IN A 73.80.65.49
dname01.example. 3600 IN DNAME dname-target.
dname02.example. 3600 IN DNAME dname-target.example.
dname03.example. 3600 IN DNAME .
dnskey01.example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
dnskey02.example. 3600 IN DNSKEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
ds01.example. 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890
e.example. 300 IN MX 10 mail.example.
e.example. 300 IN TXT "one"
e.example. 300 IN TXT "three"
e.example. 300 IN TXT "two"
e.example. 300 IN A 73.80.65.49
e.example. 300 IN A 73.80.65.50
e.example. 300 IN A 73.80.65.52
e.example. 300 IN A 73.80.65.51
f.example. 300 IN A 73.80.65.52
gpos01.example. 3600 IN GPOS -22.6882 116.8652 250.0
hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4"
hinfo02.example. 3600 IN HINFO "PC" "NetBSD"
isdn01.example. 3600 IN ISDN "isdn-address"
isdn02.example. 3600 IN ISDN "isdn-address" "subaddress"
isdn03.example. 3600 IN ISDN "isdn-address"
isdn04.example. 3600 IN ISDN "isdn-address" "subaddress"
key01.example. 3600 IN KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
key02.example. 3600 IN KEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8=
kx01.example. 3600 IN KX 10 kdc.example.
kx02.example. 3600 IN KX 10 .
loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc03.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m
loc04.example. 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
loc05.example. 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m
mx01.example. 3600 IN MX 10 mail.example.
mx02.example. 3600 IN MX 10 .
naptr01.example. 3600 IN NAPTR 0 0 "" "" "" .
naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo.
ns1.example. 300 IN A 10.53.0.1
ns2.example. 300 IN A 10.53.0.2
nsap-ptr01.example. 3600 IN NSAP-PTR foo.
nsap-ptr01.example. 3600 IN NSAP-PTR .
nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100
nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100
nsec01.example. 3600 IN NSEC a.secure. TYPE1234
nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC
nsec03.example. 3600 IN NSEC . TYPE65535
nxt01.example. 3600 IN NXT a.secure.example. NS SOA MX SIG KEY LOC NXT
nxt02.example. 3600 IN NXT . NSAP-PTR NXT
nxt03.example. 3600 IN NXT . A
nxt04.example. 3600 IN NXT . TYPE127
ptr01.example. 3600 IN PTR example.
px01.example. 3600 IN PX 65535 foo. bar.
px02.example. 3600 IN PX 65535 . .
rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example.
rp02.example. 3600 IN RP . .
rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
rt01.example. 3600 IN RT 0 intermediate-host.example.
rt02.example. 3600 IN RT 65535 .
s.example. 300 IN NS ns.s.example.
ns.s.example. 300 IN A 73.80.65.49
sig01.example. 3600 IN SIG NXT 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY=
srv01.example. 3600 IN SRV 0 0 0 .
srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
sshfp1.example. 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
t.example. 301 IN A 73.80.65.49
txt01.example. 3600 IN TXT "foo"
txt02.example. 3600 IN TXT "foo" "bar"
txt03.example. 3600 IN TXT "foo"
txt04.example. 3600 IN TXT "foo" "bar"
txt05.example. 3600 IN TXT "foo bar"
txt06.example. 3600 IN TXT "foo bar"
txt07.example. 3600 IN TXT "foo bar"
txt08.example. 3600 IN TXT "foo\010bar"
txt09.example. 3600 IN TXT "foo\010bar"
txt10.example. 3600 IN TXT "foo bar"
txt11.example. 3600 IN TXT "\"foo\""
txt12.example. 3600 IN TXT "\"foo\""
u.example. 300 IN TXT "txt-not-in-nxt"
a.u.example. 300 IN A 73.80.65.49
b.u.example. 300 IN A 73.80.65.49
unknown2.example. 3600 IN TYPE999 \# 8 0a0000010a000001
unknown3.example. 3600 IN A 127.0.0.2
wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23
wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53
wks03.example. 3600 IN WKS 10.0.0.2 6 65535
x2501.example. 3600 IN X25 "123456789"

View file

@ -0,0 +1,68 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.flags
import linkcheck.dns.rcode
import linkcheck.dns.opcode
class TestFlags (unittest.TestCase):
def test_rcode1(self):
self.assertEqual(linkcheck.dns.rcode.from_text('FORMERR'),
linkcheck.dns.rcode.FORMERR)
def test_rcode2(self):
self.assertEqual(linkcheck.dns.rcode.to_text(linkcheck.dns.rcode.FORMERR),
"FORMERR")
def test_rcode3(self):
self.assertEqual(linkcheck.dns.rcode.to_flags(linkcheck.dns.rcode.FORMERR), (1, 0))
def test_rcode4(self):
self.assertEqual(linkcheck.dns.rcode.to_flags(linkcheck.dns.rcode.BADVERS),
(0, 0x01000000))
def test_rcode6(self):
self.assertEqual(linkcheck.dns.rcode.from_flags(0, 0x01000000),
linkcheck.dns.rcode.BADVERS)
def test_rcode6(self):
self.assertEqual(linkcheck.dns.rcode.from_flags(5, 0), linkcheck.dns.rcode.REFUSED)
def test_rcode7(self):
def bad():
linkcheck.dns.rcode.to_flags(4096)
self.assertRaises(ValueError, bad)
def test_flags1(self):
self.assertEqual(linkcheck.dns.flags.from_text("RA RD AA QR"),
linkcheck.dns.flags.QR|linkcheck.dns.flags.AA|linkcheck.dns.flags.RD|linkcheck.dns.flags.RA)
def test_flags2(self):
flgs = linkcheck.dns.flags.QR|linkcheck.dns.flags.AA|linkcheck.dns.flags.RD|linkcheck.dns.flags.RA
self.assertEqual(linkcheck.dns.flags.to_text(flgs), "QR AA RD RA")
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestFlags))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,160 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.exception
import linkcheck.dns.message
query_text = """id 1234
opcode QUERY
rcode NOERROR
flags RD
edns 0
eflags DO
payload 4096
;QUESTION
wwww.dnspython.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL"""
goodhex = '04d201000001000000000001047777777709646e73707974686f6e' \
'036f726700000100010000291000000080000000'
goodwire = goodhex.decode('hex_codec')
answer_text = """id 1234
opcode QUERY
rcode NOERROR
flags QR AA RD
;QUESTION
dnspython.org. IN SOA
;ANSWER
dnspython.org. 3600 IN SOA woof.dnspython.org. hostmaster.dnspython.org. 2003052700 3600 1800 604800 3600
;AUTHORITY
dnspython.org. 3600 IN NS ns1.staff.nominum.org.
dnspython.org. 3600 IN NS ns2.staff.nominum.org.
dnspython.org. 3600 IN NS woof.play-bow.org.
;ADDITIONAL
woof.play-bow.org. 3600 IN A 204.152.186.150
"""
goodhex2 = '04d2 8500 0001 0001 0003 0001' \
'09646e73707974686f6e036f726700 0006 0001' \
'c00c 0006 0001 00000e10 0028 ' \
'04776f6f66c00c 0a686f73746d6173746572c00c' \
'7764289c 00000e10 00000708 00093a80 00000e10' \
'c00c 0002 0001 00000e10 0014' \
'036e7331057374616666076e6f6d696e756dc016' \
'c00c 0002 0001 00000e10 0006 036e7332c063' \
'c00c 0002 0001 00000e10 0010 04776f6f6608706c61792d626f77c016' \
'c091 0001 0001 00000e10 0004 cc98ba96'
goodwire2 = goodhex2.replace(' ', '').decode('hex_codec')
query_text_2 = """id 1234
opcode QUERY
rcode 4095
flags RD
edns 0
eflags DO
payload 4096
;QUESTION
wwww.dnspython.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL"""
goodhex3 = '04d2010f0001000000000001047777777709646e73707974686f6e' \
'036f726700000100010000291000ff0080000000'
goodwire3 = goodhex3.decode('hex_codec')
class TestMessage (unittest.TestCase):
def test_comparison_eq1(self):
q1 = linkcheck.dns.message.from_text(query_text)
q2 = linkcheck.dns.message.from_text(query_text)
self.assertEqual(q1, q2)
def test_comparison_ne1(self):
q1 = linkcheck.dns.message.from_text(query_text)
q2 = linkcheck.dns.message.from_text(query_text)
q2.id = 10
self.assertNotEqual(q1, q2)
def test_comparison_ne2(self):
q1 = linkcheck.dns.message.from_text(query_text)
q2 = linkcheck.dns.message.from_text(query_text)
q2.question = []
self.assertNotEqual(q1, q2)
def test_comparison_ne3(self):
q1 = linkcheck.dns.message.from_text(query_text)
self.assertNotEqual(q1, 1)
def test_EDNS_to_wire1(self):
q = linkcheck.dns.message.from_text(query_text)
w = q.to_wire()
self.assertEqual(w, goodwire)
def test_EDNS_from_wire1(self):
m = linkcheck.dns.message.from_wire(goodwire)
self.assertEqual(str(m), query_text)
def test_EDNS_to_wire2(self):
q = linkcheck.dns.message.from_text(query_text_2)
w = q.to_wire()
self.assertEqual(w, goodwire3)
def test_EDNS_from_wire2(self):
m = linkcheck.dns.message.from_wire(goodwire3)
self.assertEqual(str(m), query_text_2)
def test_TooBig(self):
def bad():
q = linkcheck.dns.message.from_text(query_text)
w = q.to_wire(max_size=15)
self.assertRaises(linkcheck.dns.exception.TooBig, bad)
def test_answer1(self):
a = linkcheck.dns.message.from_text(answer_text)
wire = a.to_wire(want_shuffle=False)
self.assertEqual(wire, goodwire2)
def test_TrailingJunk(self):
def bad():
badwire = goodwire + '\x00'
m = linkcheck.dns.message.from_wire(badwire)
self.assertRaises(linkcheck.dns.message.TrailingJunk, bad)
def test_ShortHeader(self):
def bad():
badwire = '\x00' * 11
m = linkcheck.dns.message.from_wire(badwire)
self.assertRaises(linkcheck.dns.message.ShortHeader, bad)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMessage))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,603 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import cStringIO as StringIO
import linkcheck.dns.name
class TestName (unittest.TestCase):
def setUp(self):
self.origin = linkcheck.dns.name.from_text('example.')
def testFromTextRel1(self):
n = linkcheck.dns.name.from_text('foo.bar')
self.assertEqual(n.labels, ('foo', 'bar', ''))
def testFromTextRel2(self):
n = linkcheck.dns.name.from_text('foo.bar', origin=self.origin)
self.assertEqual(n.labels, ('foo', 'bar', 'example', ''))
def testFromTextRel3(self):
n = linkcheck.dns.name.from_text('foo.bar', origin=None)
self.assertEqual(n.labels, ('foo', 'bar'))
def testFromTextRel4(self):
n = linkcheck.dns.name.from_text('@', origin=None)
self.assertEqual(n, linkcheck.dns.name.empty)
def testFromTextRel5(self):
n = linkcheck.dns.name.from_text('@', origin=self.origin)
self.assertEqual(n, self.origin)
def testFromTextAbs1(self):
n = linkcheck.dns.name.from_text('foo.bar.')
self.assertEqual(n.labels, ('foo', 'bar', ''))
def testTortureFromText(self):
good = [
r'.',
r'a',
r'a.',
r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
r'\000.\008.\010.\032.\046.\092.\099.\255',
r'\\',
r'\..\.',
r'\\.\\',
r'!"#%&/()=+-',
r'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255',
]
bad = [
r'..',
r'.a',
r'\\..',
'\\', # yes, we don't want the 'r' prefix!
r'\0',
r'\00',
r'\00Z',
r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
r'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
r'\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255.\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255',
]
for t in good:
try:
n = linkcheck.dns.name.from_text(t)
except:
self.fail("good test '%s' raised an exception" % t)
for t in bad:
caught = False
try:
n = linkcheck.dns.name.from_text(t)
except:
caught = True
if not caught:
self.fail("bad test '%s' did not raise an exception" % t)
def testImmutable1(self):
def bad():
self.origin.labels = ()
self.assertRaises(TypeError, bad)
def testImmutable2(self):
def bad():
self.origin.labels[0] = 'foo'
self.assertRaises(TypeError, bad)
def testAbs1(self):
self.assert_(linkcheck.dns.name.root.is_absolute())
def testAbs2(self):
self.assert_(not linkcheck.dns.name.empty.is_absolute())
def testAbs3(self):
self.assert_(self.origin.is_absolute())
def testAbs3(self):
n = linkcheck.dns.name.from_text('foo', origin=None)
self.assert_(not n.is_absolute())
def testWild1(self):
n = linkcheck.dns.name.from_text('*.foo', origin=None)
self.assert_(n.is_wild())
def testWild2(self):
n = linkcheck.dns.name.from_text('*a.foo', origin=None)
self.assert_(not n.is_wild())
def testWild3(self):
n = linkcheck.dns.name.from_text('a.*.foo', origin=None)
self.assert_(not n.is_wild())
def testWild4(self):
self.assert_(not linkcheck.dns.name.root.is_wild())
def testWild5(self):
self.assert_(not linkcheck.dns.name.empty.is_wild())
def testHash1(self):
n1 = linkcheck.dns.name.from_text('fOo.COM')
n2 = linkcheck.dns.name.from_text('foo.com')
self.assertEqual(hash(n1), hash(n2))
def testCompare1(self):
n1 = linkcheck.dns.name.from_text('a')
n2 = linkcheck.dns.name.from_text('b')
self.assert_(n1 < n2)
self.assert_(n2 > n1)
def testCompare2(self):
n1 = linkcheck.dns.name.from_text('')
n2 = linkcheck.dns.name.from_text('b')
self.assert_(n1 < n2)
self.assert_(n2 > n1)
def testCompare3(self):
self.assert_(linkcheck.dns.name.empty < linkcheck.dns.name.root)
self.assert_(linkcheck.dns.name.root > linkcheck.dns.name.empty)
def testCompare4(self):
self.assertNotEqual(linkcheck.dns.name.root, 1)
def testCompare5(self):
self.assert_(linkcheck.dns.name.root < 1 or linkcheck.dns.name.root > 1)
def testSubdomain1(self):
self.assert_(not linkcheck.dns.name.empty.is_subdomain(linkcheck.dns.name.root))
def testSubdomain2(self):
self.assert_(not linkcheck.dns.name.root.is_subdomain(linkcheck.dns.name.empty))
def testSubdomain3(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(n.is_subdomain(self.origin))
def testSubdomain4(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(n.is_subdomain(linkcheck.dns.name.root))
def testSubdomain5(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(n.is_subdomain(n))
def testSuperdomain1(self):
self.assert_(not linkcheck.dns.name.empty.is_superdomain(linkcheck.dns.name.root))
def testSuperdomain2(self):
self.assert_(not linkcheck.dns.name.root.is_superdomain(linkcheck.dns.name.empty))
def testSuperdomain3(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(self.origin.is_superdomain(n))
def testSuperdomain4(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(linkcheck.dns.name.root.is_superdomain(n))
def testSuperdomain5(self):
n = linkcheck.dns.name.from_text('foo', origin=self.origin)
self.assert_(n.is_superdomain(n))
def testCanonicalize1(self):
n = linkcheck.dns.name.from_text('FOO.bar', origin=self.origin)
c = n.canonicalize()
self.assertEqual(c.labels, ('foo', 'bar', 'example', ''))
def testToText1(self):
n = linkcheck.dns.name.from_text('FOO.bar', origin=self.origin)
t = n.to_text()
self.assertEqual(t, 'FOO.bar.example.')
def testToText2(self):
n = linkcheck.dns.name.from_text('FOO.bar', origin=self.origin)
t = n.to_text(True)
self.assertEqual(t, 'FOO.bar.example')
def testToText3(self):
n = linkcheck.dns.name.from_text('FOO.bar', origin=None)
t = n.to_text()
self.assertEqual(t, 'FOO.bar')
def testToText4(self):
t = linkcheck.dns.name.empty.to_text()
self.assertEqual(t, '@')
def testToText5(self):
t = linkcheck.dns.name.root.to_text()
self.assertEqual(t, '.')
def testToText6(self):
n = linkcheck.dns.name.from_text('FOO bar', origin=None)
t = n.to_text()
self.assertEqual(t, r'FOO\032bar')
def testToText7(self):
n = linkcheck.dns.name.from_text(r'FOO\.bar', origin=None)
t = n.to_text()
self.assertEqual(t, r'FOO\.bar')
def testToText8(self):
n = linkcheck.dns.name.from_text(r'\070OO\.bar', origin=None)
t = n.to_text()
self.assertEqual(t, r'FOO\.bar')
def testSlice1(self):
n = linkcheck.dns.name.from_text(r'a.b.c.', origin=None)
s = n[:]
self.assertEqual(s, ('a', 'b', 'c', ''))
def testSlice2(self):
n = linkcheck.dns.name.from_text(r'a.b.c.', origin=None)
s = n[:2]
self.assertEqual(s, ('a', 'b'))
def testSlice3(self):
n = linkcheck.dns.name.from_text(r'a.b.c.', origin=None)
s = n[2:]
self.assertEqual(s, ('c', ''))
def testEmptyLabel1(self):
def bad():
n = linkcheck.dns.name.Name(['a', '', 'b'])
self.assertRaises(linkcheck.dns.name.EmptyLabel, bad)
def testEmptyLabel2(self):
def bad():
n = linkcheck.dns.name.Name(['', 'b'])
self.assertRaises(linkcheck.dns.name.EmptyLabel, bad)
def testEmptyLabel3(self):
n = linkcheck.dns.name.Name(['b', ''])
self.assert_(n)
def testLongLabel(self):
n = linkcheck.dns.name.Name(['a' * 63])
self.assert_(n)
def testLabelTooLong(self):
def bad():
n = linkcheck.dns.name.Name(['a' * 64, 'b'])
self.assertRaises(linkcheck.dns.name.LabelTooLong, bad)
def testLongName(self):
n = linkcheck.dns.name.Name(['a' * 63, 'a' * 63, 'a' * 63, 'a' * 62])
self.assert_(n)
def testNameTooLong(self):
def bad():
n = linkcheck.dns.name.Name(['a' * 63, 'a' * 63, 'a' * 63, 'a' * 63])
self.assertRaises(linkcheck.dns.name.NameTooLong, bad)
def testConcat1(self):
n1 = linkcheck.dns.name.Name(['a', 'b'])
n2 = linkcheck.dns.name.Name(['c', 'd'])
e = linkcheck.dns.name.Name(['a', 'b', 'c', 'd'])
r = n1 + n2
self.assertEqual(r, e)
def testConcat2(self):
n1 = linkcheck.dns.name.Name(['a', 'b'])
n2 = linkcheck.dns.name.Name([])
e = linkcheck.dns.name.Name(['a', 'b'])
r = n1 + n2
self.assertEqual(r, e)
def testConcat2(self):
n1 = linkcheck.dns.name.Name([])
n2 = linkcheck.dns.name.Name(['a', 'b'])
e = linkcheck.dns.name.Name(['a', 'b'])
r = n1 + n2
self.assertEqual(r, e)
def testConcat3(self):
n1 = linkcheck.dns.name.Name(['a', 'b', ''])
n2 = linkcheck.dns.name.Name([])
e = linkcheck.dns.name.Name(['a', 'b', ''])
r = n1 + n2
self.assertEqual(r, e)
def testConcat4(self):
n1 = linkcheck.dns.name.Name(['a', 'b'])
n2 = linkcheck.dns.name.Name(['c', ''])
e = linkcheck.dns.name.Name(['a', 'b', 'c', ''])
r = n1 + n2
self.assertEqual(r, e)
def testConcat5(self):
def bad():
n1 = linkcheck.dns.name.Name(['a', 'b', ''])
n2 = linkcheck.dns.name.Name(['c'])
r = n1 + n2
self.assertRaises(linkcheck.dns.name.AbsoluteConcatenation, bad)
def testBadEscape(self):
def bad():
n = linkcheck.dns.name.from_text(r'a.b\0q1.c.')
print n
self.assertRaises(linkcheck.dns.name.BadEscape, bad)
def testDigestable1(self):
n = linkcheck.dns.name.from_text('FOO.bar')
d = n.to_digestable()
self.assertEqual(d, '\x03foo\x03bar\x00')
def testDigestable2(self):
n1 = linkcheck.dns.name.from_text('FOO.bar')
n2 = linkcheck.dns.name.from_text('foo.BAR.')
d1 = n1.to_digestable()
d2 = n2.to_digestable()
self.assertEqual(d1, d2)
def testDigestable3(self):
d = linkcheck.dns.name.root.to_digestable()
self.assertEqual(d, '\x00')
def testDigestable4(self):
n = linkcheck.dns.name.from_text('FOO.bar', None)
d = n.to_digestable(linkcheck.dns.name.root)
self.assertEqual(d, '\x03foo\x03bar\x00')
def testBadDigestable(self):
def bad():
n = linkcheck.dns.name.from_text('FOO.bar', None)
d = n.to_digestable()
self.assertRaises(linkcheck.dns.name.NeedAbsoluteNameOrOrigin, bad)
def testToWire1(self):
n = linkcheck.dns.name.from_text('FOO.bar')
f = StringIO.StringIO()
compress = {}
n.to_wire(f, compress)
self.assertEqual(f.getvalue(), '\x03FOO\x03bar\x00')
def testToWire2(self):
n = linkcheck.dns.name.from_text('FOO.bar')
f = StringIO.StringIO()
compress = {}
n.to_wire(f, compress)
n.to_wire(f, compress)
self.assertEqual(f.getvalue(), '\x03FOO\x03bar\x00\xc0\x00')
def testToWire3(self):
n1 = linkcheck.dns.name.from_text('FOO.bar')
n2 = linkcheck.dns.name.from_text('foo.bar')
f = StringIO.StringIO()
compress = {}
n1.to_wire(f, compress)
n2.to_wire(f, compress)
self.assertEqual(f.getvalue(), '\x03FOO\x03bar\x00\xc0\x00')
def testToWire4(self):
n1 = linkcheck.dns.name.from_text('FOO.bar')
n2 = linkcheck.dns.name.from_text('a.foo.bar')
f = StringIO.StringIO()
compress = {}
n1.to_wire(f, compress)
n2.to_wire(f, compress)
self.assertEqual(f.getvalue(), '\x03FOO\x03bar\x00\x01\x61\xc0\x00')
def testToWire5(self):
n1 = linkcheck.dns.name.from_text('FOO.bar')
n2 = linkcheck.dns.name.from_text('a.foo.bar')
f = StringIO.StringIO()
compress = {}
n1.to_wire(f, compress)
n2.to_wire(f, None)
self.assertEqual(f.getvalue(),
'\x03FOO\x03bar\x00\x01\x61\x03foo\x03bar\x00')
def testBadToWire(self):
def bad():
n = linkcheck.dns.name.from_text('FOO.bar', None)
f = StringIO.StringIO()
compress = {}
n.to_wire(f, compress)
self.assertRaises(linkcheck.dns.name.NeedAbsoluteNameOrOrigin, bad)
def testSplit1(self):
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(2)
ep = linkcheck.dns.name.from_text('foo', None)
es = linkcheck.dns.name.from_text('bar.', None)
self.assertEqual(prefix, ep)
self.assertEqual(suffix, es)
def testSplit2(self):
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(1)
ep = linkcheck.dns.name.from_text('foo.bar', None)
es = linkcheck.dns.name.from_text('.', None)
self.assertEqual(prefix, ep)
self.assertEqual(suffix, es)
def testSplit3(self):
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(0)
ep = linkcheck.dns.name.from_text('foo.bar.', None)
es = linkcheck.dns.name.from_text('', None)
self.assertEqual(prefix, ep)
self.assertEqual(suffix, es)
def testSplit4(self):
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(3)
ep = linkcheck.dns.name.from_text('', None)
es = linkcheck.dns.name.from_text('foo.bar.', None)
self.assertEqual(prefix, ep)
self.assertEqual(suffix, es)
def testBadSplit1(self):
def bad():
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(-1)
self.assertRaises(ValueError, bad)
def testBadSplit2(self):
def bad():
n = linkcheck.dns.name.from_text('foo.bar.')
(prefix, suffix) = n.split(4)
self.assertRaises(ValueError, bad)
def testRelativize1(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = linkcheck.dns.name.from_text('bar.', None)
e = linkcheck.dns.name.from_text('a.foo', None)
self.assert_(n.relativize(o) == e)
def testRelativize2(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = n
e = linkcheck.dns.name.empty
self.assertEqual(n.relativize(o), e)
def testRelativize3(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = linkcheck.dns.name.from_text('blaz.', None)
e = n
self.assertEqual(n.relativize(o), e)
def testRelativize4(self):
n = linkcheck.dns.name.from_text('a.foo', None)
o = linkcheck.dns.name.root
e = n
self.assertEqual(n.relativize(o), e)
def testDerelativize1(self):
n = linkcheck.dns.name.from_text('a.foo', None)
o = linkcheck.dns.name.from_text('bar.', None)
e = linkcheck.dns.name.from_text('a.foo.bar.', None)
self.assertEqual(n.derelativize(o), e)
def testDerelativize2(self):
n = linkcheck.dns.name.empty
o = linkcheck.dns.name.from_text('a.foo.bar.', None)
e = o
self.assertEqual(n.derelativize(o), e)
def testDerelativize3(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = linkcheck.dns.name.from_text('blaz.', None)
e = n
self.assertEqual(n.derelativize(o), e)
def testChooseRelativity1(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = linkcheck.dns.name.from_text('bar.', None)
e = linkcheck.dns.name.from_text('a.foo', None)
self.assertEqual(n.choose_relativity(o, True), e)
def testChooseRelativity2(self):
n = linkcheck.dns.name.from_text('a.foo.bar.', None)
o = linkcheck.dns.name.from_text('bar.', None)
e = n
self.assertEqual(n.choose_relativity(o, False), e)
def testChooseRelativity3(self):
n = linkcheck.dns.name.from_text('a.foo', None)
o = linkcheck.dns.name.from_text('bar.', None)
e = linkcheck.dns.name.from_text('a.foo.bar.', None)
self.assertEqual(n.choose_relativity(o, False), e)
def testChooseRelativity4(self):
n = linkcheck.dns.name.from_text('a.foo', None)
o = None
e = n
self.assertEqual(n.choose_relativity(o, True), e)
def testChooseRelativity5(self):
n = linkcheck.dns.name.from_text('a.foo', None)
o = None
e = n
self.assertEqual(n.choose_relativity(o, False), e)
def testChooseRelativity6(self):
n = linkcheck.dns.name.from_text('a.foo.', None)
o = None
e = n
self.assertEqual(n.choose_relativity(o, True), e)
def testChooseRelativity7(self):
n = linkcheck.dns.name.from_text('a.foo.', None)
o = None
e = n
self.assertEqual(n.choose_relativity(o, False), e)
def testFromWire1(self):
w = '\x03foo\x00\xc0\x00'
(n1, cused1) = linkcheck.dns.name.from_wire(w, 0)
(n2, cused2) = linkcheck.dns.name.from_wire(w, cused1)
en1 = linkcheck.dns.name.from_text('foo.')
en2 = en1
ecused1 = 5
ecused2 = 2
self.assertEqual(n1, en1)
self.assertEqual(cused1, ecused1)
self.assertEqual(n2, en2)
self.assertEqual(cused2, ecused2)
def testFromWire1(self):
w = '\x03foo\x00\x01a\xc0\x00\x01b\xc0\x05'
current = 0
(n1, cused1) = linkcheck.dns.name.from_wire(w, current)
current += cused1
(n2, cused2) = linkcheck.dns.name.from_wire(w, current)
current += cused2
(n3, cused3) = linkcheck.dns.name.from_wire(w, current)
en1 = linkcheck.dns.name.from_text('foo.')
en2 = linkcheck.dns.name.from_text('a.foo.')
en3 = linkcheck.dns.name.from_text('b.a.foo.')
ecused1 = 5
ecused2 = 4
ecused3 = 4
self.assertEqual(n1, en1)
self.assertEqual(cused1, ecused1)
self.assertEqual(n2, en2)
self.assertEqual(cused2, ecused2)
self.assertEqual(n3, en3)
self.assertEqual(cused3, ecused3)
def testBadFromWire1(self):
def bad():
w = '\x03foo\xc0\x04'
(n, cused) = linkcheck.dns.name.from_wire(w, 0)
self.assertRaises(linkcheck.dns.name.BadPointer, bad)
def testBadFromWire2(self):
def bad():
w = '\x03foo\xc0\x05'
(n, cused) = linkcheck.dns.name.from_wire(w, 0)
self.assertRaises(linkcheck.dns.name.BadPointer, bad)
def testBadFromWire3(self):
def bad():
w = '\xbffoo'
(n, cused) = linkcheck.dns.name.from_wire(w, 0)
self.assertRaises(linkcheck.dns.name.BadLabelType, bad)
def testBadFromWire4(self):
def bad():
w = '\x41foo'
(n, cused) = linkcheck.dns.name.from_wire(w, 0)
self.assertRaises(linkcheck.dns.name.BadLabelType, bad)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestName))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,110 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.name
import linkcheck.dns.namedict
class TestNameDict (unittest.TestCase):
def setUp(self):
self.ndict = linkcheck.dns.namedict.NameDict()
n1 = linkcheck.dns.name.from_text('foo.bar.')
n2 = linkcheck.dns.name.from_text('bar.')
self.ndict[n1] = 1
self.ndict[n2] = 2
self.rndict = linkcheck.dns.namedict.NameDict()
n1 = linkcheck.dns.name.from_text('foo.bar', None)
n2 = linkcheck.dns.name.from_text('bar', None)
self.rndict[n1] = 1
self.rndict[n2] = 2
def testDepth(self):
self.assertEqual(self.ndict.max_depth, 3)
def testLookup1(self):
k = linkcheck.dns.name.from_text('foo.bar.')
self.assertEqual(self.ndict[k], 1)
def testLookup2(self):
k = linkcheck.dns.name.from_text('foo.bar.')
self.assertEqual(self.ndict.get_deepest_match(k)[1], 1)
def testLookup3(self):
k = linkcheck.dns.name.from_text('a.b.c.foo.bar.')
self.assertEqual(self.ndict.get_deepest_match(k)[1], 1)
def testLookup4(self):
k = linkcheck.dns.name.from_text('a.b.c.bar.')
self.assertEqual(self.ndict.get_deepest_match(k)[1], 2)
def testLookup5(self):
def bad():
n = linkcheck.dns.name.from_text('a.b.c.')
(k, v) = self.ndict.get_deepest_match(n)
self.assertRaises(KeyError, bad)
def testLookup6(self):
def bad():
(k, v) = self.ndict.get_deepest_match(linkcheck.dns.name.empty)
self.assertRaises(KeyError, bad)
def testLookup7(self):
self.ndict[linkcheck.dns.name.empty] = 100
n = linkcheck.dns.name.from_text('a.b.c.')
(k, v) = self.ndict.get_deepest_match(n)
self.assertEqual(v, 100)
def testLookup8(self):
def bad():
self.ndict['foo'] = 100
self.assertRaises(ValueError, bad)
def testRelDepth(self):
self.assertEqual(self.rndict.max_depth, 2)
def testRelLookup1(self):
k = linkcheck.dns.name.from_text('foo.bar', None)
self.assertEqual(self.rndict[k], 1)
def testRelLookup2(self):
k = linkcheck.dns.name.from_text('foo.bar', None)
self.assertEqual(self.rndict.get_deepest_match(k)[1], 1)
def testRelLookup3(self):
k = linkcheck.dns.name.from_text('a.b.c.foo.bar', None)
self.assertEqual(self.rndict.get_deepest_match(k)[1], 1)
def testRelLookup4(self):
k = linkcheck.dns.name.from_text('a.b.c.bar', None)
self.assertEqual(self.rndict.get_deepest_match(k)[1], 2)
def testRelLookup7(self):
self.rndict[linkcheck.dns.name.empty] = 100
n = linkcheck.dns.name.from_text('a.b.c', None)
(k, v) = self.rndict.get_deepest_match(n)
self.assertEqual(v, 100)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestNameDict))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,164 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.exception
import linkcheck.dns.ipv6
class TestNtoAAtoN (unittest.TestCase):
def test_aton1(self):
a = linkcheck.dns.ipv6.inet_aton('::')
self.assertEqual(a, '\x00' * 16)
def test_aton2(self):
a = linkcheck.dns.ipv6.inet_aton('::1')
self.assertEqual(a, '\x00' * 15 + '\x01')
def test_aton3(self):
a = linkcheck.dns.ipv6.inet_aton('::10.0.0.1')
self.assertEqual(a, '\x00' * 12 + '\x0a\x00\x00\x01')
def test_aton4(self):
a = linkcheck.dns.ipv6.inet_aton('abcd::dcba')
self.assertEqual(a, '\xab\xcd' + '\x00' * 12 + '\xdc\xba')
def test_aton5(self):
a = linkcheck.dns.ipv6.inet_aton('1:2:3:4:5:6:7:8')
self.assertEqual(a,
'00010002000300040005000600070008'.decode('hex_codec'))
def test_bad_aton1(self):
def bad():
a = linkcheck.dns.ipv6.inet_aton('abcd:dcba')
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def test_bad_aton2(self):
def bad():
a = linkcheck.dns.ipv6.inet_aton('abcd::dcba::1')
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def test_bad_aton3(self):
def bad():
a = linkcheck.dns.ipv6.inet_aton('1:2:3:4:5:6:7:8:9')
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def test_aton1(self):
a = linkcheck.dns.ipv6.inet_aton('::')
self.assertEqual(a, '\x00' * 16)
def test_aton2(self):
a = linkcheck.dns.ipv6.inet_aton('::1')
self.assertEqual(a, '\x00' * 15 + '\x01')
def test_aton3(self):
a = linkcheck.dns.ipv6.inet_aton('::10.0.0.1')
self.assertEqual(a, '\x00' * 12 + '\x0a\x00\x00\x01')
def test_aton4(self):
a = linkcheck.dns.ipv6.inet_aton('abcd::dcba')
self.assertEqual(a, '\xab\xcd' + '\x00' * 12 + '\xdc\xba')
def test_ntoa1(self):
b = '00010002000300040005000600070008'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '1:2:3:4:5:6:7:8')
def test_ntoa2(self):
b = '\x00' * 16
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::')
def test_ntoa3(self):
b = '\x00' * 15 + '\x01'
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::1')
def test_ntoa4(self):
b = '\x80' + '\x00' * 15
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '8000::')
def test_ntoa5(self):
b = '\x01\xcd' + '\x00' * 12 + '\x03\xef'
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '1cd::3ef')
def test_ntoa6(self):
b = 'ffff00000000ffff000000000000ffff'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, 'ffff:0:0:ffff::ffff')
def test_ntoa7(self):
b = '00000000ffff000000000000ffffffff'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '0:0:ffff::ffff:ffff')
def test_ntoa8(self):
b = 'ffff0000ffff00000000ffff00000000'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, 'ffff:0:ffff::ffff:0:0')
def test_ntoa9(self):
b = '0000000000000000000000000a000001'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::10.0.0.1')
def test_ntoa10(self):
b = '0000000000000000000000010a000001'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::1:a00:1')
def test_ntoa11(self):
b = '00000000000000000000ffff0a000001'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::ffff:10.0.0.1')
def test_ntoa12(self):
b = '000000000000000000000000ffffffff'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::255.255.255.255')
def test_ntoa13(self):
b = '00000000000000000000ffffffffffff'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::ffff:255.255.255.255')
def test_ntoa14(self):
b = '0000000000000000000000000001ffff'.decode('hex_codec')
t = linkcheck.dns.ipv6.inet_ntoa(b)
self.assertEqual(t, '::0.1.255.255')
def test_bad_ntoa1(self):
def bad():
a = linkcheck.dns.ipv6.inet_ntoa('')
self.assertRaises(ValueError, bad)
def test_bad_ntoa2(self):
def bad():
a = linkcheck.dns.ipv6.inet_ntoa('\x00' * 17)
self.assertRaises(ValueError, bad)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestNtoAAtoN))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,133 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
class TestRdTypeAndClass (unittest.TestCase):
# Classes
def test_class_meta1(self):
self.assert_(linkcheck.dns.rdataclass.is_metaclass(linkcheck.dns.rdataclass.ANY))
def test_class_meta2(self):
self.assert_(not linkcheck.dns.rdataclass.is_metaclass(linkcheck.dns.rdataclass.IN))
def test_class_bytext1(self):
self.assertEqual(linkcheck.dns.rdataclass.from_text('IN'),
linkcheck.dns.rdataclass.IN)
def test_class_bytext2(self):
self.assertEqual(linkcheck.dns.rdataclass.from_text('CLASS1'),
linkcheck.dns.rdataclass.IN)
def test_class_bytext_bounds1(self):
self.assertEqual(linkcheck.dns.rdataclass.from_text('CLASS0'), 0)
self.assertEqual(linkcheck.dns.rdataclass.from_text('CLASS65535'), 65535)
def test_class_bytext_bounds2(self):
def bad():
junk = linkcheck.dns.rdataclass.from_text('CLASS65536')
self.assertRaises(ValueError, bad)
def test_class_bytext_unknown(self):
def bad():
junk = linkcheck.dns.rdataclass.from_text('XXX')
self.assertRaises(linkcheck.dns.rdataclass.UnknownRdataclass, bad)
def test_class_totext1(self):
self.assertEqual(linkcheck.dns.rdataclass.to_text(linkcheck.dns.rdataclass.IN),
'IN')
def test_class_totext1(self):
self.assertEqual(linkcheck.dns.rdataclass.to_text(999), 'CLASS999')
def test_class_totext_bounds1(self):
def bad():
junk = linkcheck.dns.rdataclass.to_text(-1)
self.assertRaises(ValueError, bad)
def test_class_totext_bounds2(self):
def bad():
junk = linkcheck.dns.rdataclass.to_text(65536)
self.assertRaises(ValueError, bad)
# Types
def test_type_meta1(self):
self.assert_(linkcheck.dns.rdatatype.is_metatype(linkcheck.dns.rdatatype.ANY))
def test_type_meta2(self):
self.assert_(linkcheck.dns.rdatatype.is_metatype(linkcheck.dns.rdatatype.OPT))
def test_type_meta3(self):
self.assert_(not linkcheck.dns.rdatatype.is_metatype(linkcheck.dns.rdatatype.A))
def test_type_singleton1(self):
self.assert_(linkcheck.dns.rdatatype.is_singleton(linkcheck.dns.rdatatype.SOA))
def test_type_singleton2(self):
self.assert_(not linkcheck.dns.rdatatype.is_singleton(linkcheck.dns.rdatatype.A))
def test_type_bytext1(self):
self.assertEqual(linkcheck.dns.rdatatype.from_text('A'), linkcheck.dns.rdatatype.A)
def test_type_bytext2(self):
self.assertEqual(linkcheck.dns.rdatatype.from_text('TYPE1'),
linkcheck.dns.rdatatype.A)
def test_type_bytext_bounds1(self):
self.assertEqual(linkcheck.dns.rdatatype.from_text('TYPE0'), 0)
self.assertEqual(linkcheck.dns.rdatatype.from_text('TYPE65535'), 65535)
def test_type_bytext_bounds2(self):
def bad():
junk = linkcheck.dns.rdatatype.from_text('TYPE65536')
self.assertRaises(ValueError, bad)
def test_type_bytext_unknown(self):
def bad():
junk = linkcheck.dns.rdatatype.from_text('XXX')
self.assertRaises(linkcheck.dns.rdatatype.UnknownRdatatype, bad)
def test_type_totext1(self):
self.assertEqual(linkcheck.dns.rdatatype.to_text(linkcheck.dns.rdatatype.A), 'A')
def test_type_totext1(self):
self.assertEqual(linkcheck.dns.rdatatype.to_text(999), 'TYPE999')
def test_type_totext_bounds1(self):
def bad():
junk = linkcheck.dns.rdatatype.to_text(-1)
self.assertRaises(ValueError, bad)
def test_type_totext_bounds2(self):
def bad():
junk = linkcheck.dns.rdatatype.to_text(65536)
self.assertRaises(ValueError, bad)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestRdTypeAndClass))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,89 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO as StringIO
import sys
import time
import unittest
import linkcheck.dns.name
import linkcheck.dns.message
import linkcheck.dns.name
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
import linkcheck.dns.resolver
resolv_conf = """
/t/t
# comment 1
; comment 2
domain foo
nameserver 10.0.0.1
nameserver 10.0.0.2
"""
message_text = """id 1234
opcode QUERY
rcode NOERROR
flags QR AA RD
;QUESTION
example. IN A
;ANSWER
example. 1 IN A 10.0.0.1
;AUTHORITY
;ADDITIONAL
"""
class TestResolver (unittest.TestCase):
if sys.platform != 'win32':
def testRead(self):
f = StringIO.StringIO(resolv_conf)
r = linkcheck.dns.resolver.Resolver(f)
self.assertEqual(r.nameservers, ['10.0.0.1', '10.0.0.2'])
self.assertEqual(r.domain, linkcheck.dns.name.from_text('foo'))
def testCacheExpiration(self):
message = linkcheck.dns.message.from_text(message_text)
name = linkcheck.dns.name.from_text('example.')
answer = linkcheck.dns.resolver.Answer(name, linkcheck.dns.rdatatype.A, linkcheck.dns.rdataclass.IN,
message)
cache = linkcheck.dns.resolver.Cache()
cache.put((name, linkcheck.dns.rdatatype.A, linkcheck.dns.rdataclass.IN), answer)
time.sleep(2)
self.assert_(cache.get((name, linkcheck.dns.rdatatype.A,
linkcheck.dns.rdataclass.IN)) is None)
def testCacheCleaning(self):
message = linkcheck.dns.message.from_text(message_text)
name = linkcheck.dns.name.from_text('example.')
answer = linkcheck.dns.resolver.Answer(name, linkcheck.dns.rdatatype.A, linkcheck.dns.rdataclass.IN,
message)
cache = linkcheck.dns.resolver.Cache(cleaning_interval=1.0)
cache.put((name, linkcheck.dns.rdatatype.A, linkcheck.dns.rdataclass.IN), answer)
time.sleep(2)
self.assert_(cache.get((name, linkcheck.dns.rdatatype.A,
linkcheck.dns.rdataclass.IN)) is None)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestResolver))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,62 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.rrset
class TestRRset (unittest.TestCase):
def testEqual1(self):
r1 = linkcheck.dns.rrset.from_text('foo', 300, 'in', 'a', '10.0.0.1', '10.0.0.2')
r2 = linkcheck.dns.rrset.from_text('FOO', 300, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertEqual(r1, r2)
def testEqual2(self):
r1 = linkcheck.dns.rrset.from_text('foo', 300, 'in', 'a', '10.0.0.1', '10.0.0.2')
r2 = linkcheck.dns.rrset.from_text('FOO', 600, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertEqual(r1, r2)
def testNotEqual1(self):
r1 = linkcheck.dns.rrset.from_text('fooa', 30, 'in', 'a', '10.0.0.1', '10.0.0.2')
r2 = linkcheck.dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertNotEqual(r1, r2)
def testNotEqual2(self):
r1 = linkcheck.dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1', '10.0.0.3')
r2 = linkcheck.dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertNotEqual(r1, r2)
def testNotEqual3(self):
r1 = linkcheck.dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1', '10.0.0.2',
'10.0.0.3')
r2 = linkcheck.dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertNotEqual(r1, r2)
def testNotEqual4(self):
r1 = linkcheck.dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1')
r2 = linkcheck.dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1')
self.assertNotEqual(r1, r2)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestRRset))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,181 @@
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
# for convenience
from linkcheck.dns.set import Set as S
class TestSet(unittest.TestCase):
def testLen1(self):
s1 = S()
self.failUnless(len(s1) == 0)
def testLen2(self):
s1 = S([1, 2, 3])
self.failUnless(len(s1) == 3)
def testLen3(self):
s1 = S([1, 2, 3, 3, 3])
self.failUnless(len(s1) == 3)
def testUnion1(self):
s1 = S([1, 2, 3])
s2 = S([1, 2, 3])
e = S([1, 2, 3])
self.failUnless(s1 | s2 == e)
def testUnion2(self):
s1 = S([1, 2, 3])
s2 = S([])
e = S([1, 2, 3])
self.failUnless(s1 | s2 == e)
def testUnion3(self):
s1 = S([1, 2, 3])
s2 = S([3, 4])
e = S([1, 2, 3, 4])
self.failUnless(s1 | s2 == e)
def testIntersection1(self):
s1 = S([1, 2, 3])
s2 = S([1, 2, 3])
e = S([1, 2, 3])
self.failUnless(s1 & s2 == e)
def testIntersection2(self):
s1 = S([0, 1, 2, 3])
s2 = S([1, 2, 3, 4])
e = S([1, 2, 3])
self.failUnless(s1 & s2 == e)
def testIntersection3(self):
s1 = S([1, 2, 3])
s2 = S([])
e = S([])
self.failUnless(s1 & s2 == e)
def testIntersection4(self):
s1 = S([1, 2, 3])
s2 = S([5, 4])
e = S([])
self.failUnless(s1 & s2 == e)
def testDifference1(self):
s1 = S([1, 2, 3])
s2 = S([5, 4])
e = S([1, 2, 3])
self.failUnless(s1 - s2 == e)
def testDifference2(self):
s1 = S([1, 2, 3])
s2 = S([])
e = S([1, 2, 3])
self.failUnless(s1 - s2 == e)
def testDifference3(self):
s1 = S([1, 2, 3])
s2 = S([3, 2])
e = S([1])
self.failUnless(s1 - s2 == e)
def testDifference4(self):
s1 = S([1, 2, 3])
s2 = S([3, 2, 1])
e = S([])
self.failUnless(s1 - s2 == e)
def testSubset1(self):
s1 = S([1, 2, 3])
s2 = S([3, 2, 1])
self.failUnless(s1.issubset(s2))
def testSubset2(self):
s1 = S([1, 2, 3])
self.failUnless(s1.issubset(s1))
def testSubset3(self):
s1 = S([])
s2 = S([1, 2, 3])
self.failUnless(s1.issubset(s2))
def testSubset4(self):
s1 = S([1])
s2 = S([1, 2, 3])
self.failUnless(s1.issubset(s2))
def testSubset5(self):
s1 = S([])
s2 = S([])
self.failUnless(s1.issubset(s2))
def testSubset6(self):
s1 = S([1, 4])
s2 = S([1, 2, 3])
self.failUnless(not s1.issubset(s2))
def testSuperset1(self):
s1 = S([1, 2, 3])
s2 = S([3, 2, 1])
self.failUnless(s1.issuperset(s2))
def testSuperset2(self):
s1 = S([1, 2, 3])
self.failUnless(s1.issuperset(s1))
def testSuperset3(self):
s1 = S([1, 2, 3])
s2 = S([])
self.failUnless(s1.issuperset(s2))
def testSuperset4(self):
s1 = S([1, 2, 3])
s2 = S([1])
self.failUnless(s1.issuperset(s2))
def testSuperset5(self):
s1 = S([])
s2 = S([])
self.failUnless(s1.issuperset(s2))
def testSuperset6(self):
s1 = S([1, 2, 3])
s2 = S([1, 4])
self.failUnless(not s1.issuperset(s2))
def testUpdate1(self):
s1 = S([1, 2, 3])
u = (4, 5, 6)
e = S([1, 2, 3, 4, 5, 6])
s1.update(u)
self.failUnless(s1 == e)
def testUpdate2(self):
s1 = S([1, 2, 3])
u = []
e = S([1, 2, 3])
s1.update(u)
self.failUnless(s1 == e)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestSet))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,184 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.exception
import linkcheck.dns.tokenizer
class TestTokenizer (unittest.TestCase):
def testQuotedString1(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'"foo"')
(ttype, value) = tok.get()
self.assertEqual(ttype, linkcheck.dns.tokenizer.QUOTED_STRING)
self.assertEqual(value, 'foo')
def testQuotedString2(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'""')
(ttype, value) = tok.get()
self.assertEqual(ttype, linkcheck.dns.tokenizer.QUOTED_STRING)
self.assertEqual(value, '')
def testQuotedString3(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'"\"foo\""')
(ttype, value) = tok.get()
self.assertEqual(ttype, linkcheck.dns.tokenizer.QUOTED_STRING)
self.assertEqual(value, '"foo"')
def testQuotedString4(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'"foo\010bar"')
(ttype, value) = tok.get()
self.assertEqual(ttype, linkcheck.dns.tokenizer.QUOTED_STRING)
self.assertEqual(value, 'foo\x0abar')
def testQuotedString5(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer(r'"foo')
(ttype, value) = tok.get()
self.assertRaises(linkcheck.dns.exception.UnexpectedEnd, bad)
def testQuotedString6(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer(r'"foo\01')
(ttype, value) = tok.get()
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def testQuotedString7(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer('"foo\nbar"')
(ttype, value) = tok.get()
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def testEmpty1(self):
tok = linkcheck.dns.tokenizer.Tokenizer('')
(ttype, value) = tok.get()
self.assertEqual(ttype, linkcheck.dns.tokenizer.EOF)
def testEmpty2(self):
tok = linkcheck.dns.tokenizer.Tokenizer('')
(ttype1, value1) = tok.get()
(ttype2, value2) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.EOF)
self.assertEqual(ttype2, linkcheck.dns.tokenizer.EOF)
def testEOL(self):
tok = linkcheck.dns.tokenizer.Tokenizer('\n')
(ttype1, value1) = tok.get()
(ttype2, value2) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.EOL)
self.assertEqual(ttype2, linkcheck.dns.tokenizer.EOF)
def testWS1(self):
tok = linkcheck.dns.tokenizer.Tokenizer(' \n')
(ttype1, value1) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.EOL)
def testWS2(self):
tok = linkcheck.dns.tokenizer.Tokenizer(' \n')
(ttype1, value1) = tok.get(want_leading=True)
self.assertEqual(ttype1, linkcheck.dns.tokenizer.WHITESPACE)
def testComment1(self):
tok = linkcheck.dns.tokenizer.Tokenizer(' ;foo\n')
(ttype1, value1) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.EOL)
def testComment2(self):
tok = linkcheck.dns.tokenizer.Tokenizer(' ;foo\n')
(ttype1, value1) = tok.get(want_comment = True)
(ttype2, value2) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.COMMENT)
self.assertEqual(value1, 'foo')
self.assertEqual(ttype2, linkcheck.dns.tokenizer.EOL)
def testComment3(self):
tok = linkcheck.dns.tokenizer.Tokenizer(' ;foo bar\n')
(ttype1, value1) = tok.get(want_comment = True)
(ttype2, value2) = tok.get()
self.assertEqual(ttype1, linkcheck.dns.tokenizer.COMMENT)
self.assertEqual(value1, 'foo bar')
self.assertEqual(ttype2, linkcheck.dns.tokenizer.EOL)
def testMultiline1(self):
tok = linkcheck.dns.tokenizer.Tokenizer('( foo\n\n bar\n)')
tokens = list(iter(tok))
self.assertEqual(tokens, [(linkcheck.dns.tokenizer.IDENTIFIER, 'foo'),
(linkcheck.dns.tokenizer.IDENTIFIER, 'bar')])
def testMultiline2(self):
tok = linkcheck.dns.tokenizer.Tokenizer('( foo\n\n bar\n)\n')
tokens = list(iter(tok))
self.assertEqual(tokens, [(linkcheck.dns.tokenizer.IDENTIFIER, 'foo'),
(linkcheck.dns.tokenizer.IDENTIFIER, 'bar'),
(linkcheck.dns.tokenizer.EOL, '\n')])
def testMultiline3(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer('foo)')
tokens = list(iter(tok))
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def testMultiline4(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer('((foo)')
tokens = list(iter(tok))
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def testUnget1(self):
tok = linkcheck.dns.tokenizer.Tokenizer('foo')
t1 = tok.get()
tok.unget(t1)
t2 = tok.get()
self.assertEqual(t1, t2)
self.assertEqual(t1, (linkcheck.dns.tokenizer.IDENTIFIER, 'foo'))
def testUnget2(self):
def bad():
tok = linkcheck.dns.tokenizer.Tokenizer('foo')
t1 = tok.get()
tok.unget(t1)
tok.unget(t1)
self.assertRaises(linkcheck.dns.tokenizer.UngetBufferFull, bad)
def testGetEOL1(self):
tok = linkcheck.dns.tokenizer.Tokenizer('\n')
t = tok.get_eol()
self.assertEqual(t, '\n')
def testGetEOL2(self):
tok = linkcheck.dns.tokenizer.Tokenizer('')
t = tok.get_eol()
self.assertEqual(t, '')
def testEscapedDelimiter1(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'ch\ ld')
t = tok.get()
self.assertEqual(t, (linkcheck.dns.tokenizer.IDENTIFIER, r'ch ld'))
def testEscapedDelimiter2(self):
tok = linkcheck.dns.tokenizer.Tokenizer(r'ch\0ld')
t = tok.get()
self.assertEqual(t, (linkcheck.dns.tokenizer.IDENTIFIER, r'ch\0ld'))
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestTokenizer))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,122 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import unittest
import linkcheck.dns.update
import linkcheck.dns.rdata
import linkcheck.dns.rdataset
goodhex = '0001 2800 0001 0005 0007 0000' \
'076578616d706c6500 0006 0001' \
'03666f6fc00c 00ff 00ff 00000000 0000' \
'c019 0001 00ff 00000000 0000' \
'03626172c00c 0001 0001 00000000 0004 0a000005' \
'05626c617a32c00c 00ff 00fe 00000000 0000' \
'c049 0001 00fe 00000000 0000' \
'c019 0001 00ff 00000000 0000' \
'c019 0001 0001 0000012c 0004 0a000001' \
'c019 0001 0001 0000012c 0004 0a000002' \
'c035 0001 0001 0000012c 0004 0a000003' \
'c035 0001 00fe 00000000 0004 0a000004' \
'04626c617ac00c 0001 00ff 00000000 0000' \
'c049 00ff 00ff 00000000 0000'
goodwire = goodhex.replace(' ', '').decode('hex_codec')
update_text="""id 1
opcode UPDATE
rcode NOERROR
;ZONE
example. IN SOA
;PREREQ
foo ANY ANY
foo ANY A
bar 0 IN A 10.0.0.5
blaz2 NONE ANY
blaz2 NONE A
;UPDATE
foo ANY A
foo 300 IN A 10.0.0.1
foo 300 IN A 10.0.0.2
bar 300 IN A 10.0.0.3
bar 0 NONE A 10.0.0.4
blaz ANY A
blaz2 ANY ANY
"""
class TestUpdate (unittest.TestCase):
def test_to_wire1(self):
update = linkcheck.dns.update.Update('example')
update.id = 1
update.present('foo')
update.present('foo', 'a')
update.present('bar', 'a', '10.0.0.5')
update.absent('blaz2')
update.absent('blaz2', 'a')
update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2')
update.add('bar', 300, 'a', '10.0.0.3')
update.delete('bar', 'a', '10.0.0.4')
update.delete('blaz','a')
update.delete('blaz2')
self.assertEqual(update.to_wire(), goodwire)
def test_to_wire2(self):
update = linkcheck.dns.update.Update('example')
update.id = 1
update.present('foo')
update.present('foo', 'a')
update.present('bar', 'a', '10.0.0.5')
update.absent('blaz2')
update.absent('blaz2', 'a')
update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2')
update.add('bar', 300, linkcheck.dns.rdata.from_text(1, 1, '10.0.0.3'))
update.delete('bar', 'a', '10.0.0.4')
update.delete('blaz','a')
update.delete('blaz2')
self.assertEqual(update.to_wire(), goodwire)
def test_to_wire3(self):
update = linkcheck.dns.update.Update('example')
update.id = 1
update.present('foo')
update.present('foo', 'a')
update.present('bar', 'a', '10.0.0.5')
update.absent('blaz2')
update.absent('blaz2', 'a')
update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2')
update.add('bar', linkcheck.dns.rdataset.from_text(1, 1, 300, '10.0.0.3'))
update.delete('bar', 'a', '10.0.0.4')
update.delete('blaz','a')
update.delete('blaz2')
self.assertEqual(update.to_wire(), goodwire)
def test_from_text1(self):
update = linkcheck.dns.message.from_text(update_text)
w = update.to_wire(origin=linkcheck.dns.name.from_text('example'),
want_shuffle=False)
self.assertEqual(w, goodwire)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestUpdate))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,360 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO as StringIO
import filecmp
import os
import unittest
import linkcheck.dns.exception
import linkcheck.dns.rdata
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
import linkcheck.dns.rrset
import linkcheck.dns.zone
def fname (name):
return os.path.join("linkcheck", "dns", "tests", name)
example_text = """$TTL 3600
$ORIGIN example.
@ soa foo bar 1 2 3 4 5
@ ns ns1
@ ns ns2
ns1 a 10.0.0.1
ns2 a 10.0.0.2
$TTL 300
$ORIGIN foo.example.
bar mx 0 blaz
"""
example_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5
@ 3600 IN NS ns1
@ 3600 IN NS ns2
bar.foo 300 IN MX 0 blaz.foo
ns1 3600 IN A 10.0.0.1
ns2 3600 IN A 10.0.0.2
"""
something_quite_similar = """@ 3600 IN SOA foo bar 1 2 3 4 5
@ 3600 IN NS ns1
@ 3600 IN NS ns2
bar.foo 300 IN MX 0 blaz.foo
ns1 3600 IN A 10.0.0.1
ns2 3600 IN A 10.0.0.3
"""
something_different = """@ 3600 IN SOA fooa bar 1 2 3 4 5
@ 3600 IN NS ns11
@ 3600 IN NS ns21
bar.fooa 300 IN MX 0 blaz.fooa
ns11 3600 IN A 10.0.0.11
ns21 3600 IN A 10.0.0.21
"""
ttl_example_text = """$TTL 1h
$ORIGIN example.
@ soa foo bar 1 2 3 4 5
@ ns ns1
@ ns ns2
ns1 1d1s a 10.0.0.1
ns2 1w1D1h1m1S a 10.0.0.2
"""
no_soa_text = """$TTL 1h
$ORIGIN example.
@ ns ns1
@ ns ns2
ns1 1d1s a 10.0.0.1
ns2 1w1D1h1m1S a 10.0.0.2
"""
no_ns_text = """$TTL 1h
$ORIGIN example.
@ soa foo bar 1 2 3 4 5
"""
include_text = """$INCLUDE "%s"
""" % fname("example")
bad_directive_text = """$FOO bar
$ORIGIN example.
@ soa foo bar 1 2 3 4 5
@ ns ns1
@ ns ns2
ns1 1d1s a 10.0.0.1
ns2 1w1D1h1m1S a 10.0.0.2
"""
class TestZone (unittest.TestCase):
def testFromFile1(self):
z = linkcheck.dns.zone.from_file(fname('example'), 'example')
ok = False
try:
z.to_file(fname('example1.out'), nl='\x0a')
ok = filecmp.cmp(fname('example1.out'), fname('example1.good'))
finally:
os.unlink(fname('example1.out'))
self.assert_(ok)
def testFromFile2(self):
z = linkcheck.dns.zone.from_file(fname('example'), 'example',
relativize=False)
ok = False
try:
z.to_file(fname('example2.out'), relativize=False, nl='\x0a')
ok = filecmp.cmp(fname('example2.out'), fname('example2.good'))
finally:
os.unlink(fname('example2.out'))
self.assert_(ok)
def testFromText(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
f = StringIO.StringIO()
names = z.nodes.keys()
names.sort()
for n in names:
print >> f, z[n].to_text(n)
self.assertEqual(f.getvalue(), example_text_output)
def testTorture1(self):
#
# Read a zone containing all our supported RR types, and
# for each RR in the zone, convert the rdata into wire format
# and then back out, and see if we get equal rdatas.
#
f = StringIO.StringIO()
o = linkcheck.dns.name.from_text('example.')
z = linkcheck.dns.zone.from_file(fname('example'), o)
for (name, node) in z.iteritems():
for rds in node:
for rd in rds:
f.seek(0)
f.truncate()
rd.to_wire(f, origin=o)
wire = f.getvalue()
rd2 = linkcheck.dns.rdata.from_wire(rds.rdclass, rds.rdtype,
wire, 0, len(wire),
origin = o)
self.assertEqual(rd, rd2)
def testEqual(self):
z1 = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z2 = linkcheck.dns.zone.from_text(example_text_output, 'example.',
relativize=True)
self.assertEqual(z1, z2)
def testNotEqual1(self):
z1 = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z2 = linkcheck.dns.zone.from_text(something_quite_similar, 'example.',
relativize=True)
self.assertNotEqual(z1, z2)
def testNotEqual2(self):
z1 = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z2 = linkcheck.dns.zone.from_text(something_different, 'example.',
relativize=True)
self.assertNotEqual(z1, z2)
def testNotEqual3(self):
z1 = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z2 = linkcheck.dns.zone.from_text(something_different, 'example2.',
relativize=True)
self.assertNotEqual(z1, z2)
def testFindRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rds = z.find_rdataset('@', 'soa')
exrds = linkcheck.dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
self.assertEqual(rds, exrds)
def testFindRdataset2(self):
def bad():
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rds = z.find_rdataset('@', 'loc')
self.assertRaises(KeyError, bad)
def testFindRRset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rrs = z.find_rrset('@', 'soa')
exrrs = linkcheck.dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5')
self.assertEqual(rrs, exrrs)
def testFindRRset2(self):
def bad():
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rrs = z.find_rrset('@', 'loc')
self.assertRaises(KeyError, bad)
def testGetRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rds = z.get_rdataset('@', 'soa')
exrds = linkcheck.dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
self.assertEqual(rds, exrds)
def testGetRdataset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rds = z.get_rdataset('@', 'loc')
self.assertEqual(rds, None)
def testGetRRset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rrs = z.get_rrset('@', 'soa')
exrrs = linkcheck.dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5')
self.assertEqual(rrs, exrrs)
def testGetRRset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rrs = z.get_rrset('@', 'loc')
self.assertEqual(rrs, None)
def testReplaceRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rdataset = linkcheck.dns.rdataset.from_text('in', 'ns', 300, 'ns3', 'ns4')
z.replace_rdataset('@', rdataset)
rds = z.get_rdataset('@', 'ns')
self.assert_(rds is rdataset)
def testReplaceRdataset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
rdataset = linkcheck.dns.rdataset.from_text('in', 'txt', 300, '"foo"')
z.replace_rdataset('@', rdataset)
rds = z.get_rdataset('@', 'txt')
self.assert_(rds is rdataset)
def testDeleteRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z.delete_rdataset('@', 'ns')
rds = z.get_rdataset('@', 'ns')
self.assert_(rds is None)
def testDeleteRdataset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
z.delete_rdataset('ns1', 'a')
node = z.get_node('ns1')
self.assert_(node is None)
def testNodeFindRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.find_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.SOA)
exrds = linkcheck.dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
self.assertEqual(rds, exrds)
def testNodeFindRdataset2(self):
def bad():
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.find_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.LOC)
self.assertRaises(KeyError, bad)
def testNodeGetRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.SOA)
exrds = linkcheck.dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
self.assertEqual(rds, exrds)
def testNodeGetRdataset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.LOC)
self.assertEqual(rds, None)
def testNodeDeleteRdataset1(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.delete_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.SOA)
rds = node.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.SOA)
self.assertEqual(rds, None)
def testNodeDeleteRdataset2(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
node = z['@']
rds = node.delete_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.LOC)
rds = node.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.LOC)
self.assertEqual(rds, None)
def testIterateRdatasets(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
ns = [n for n, r in z.iterate_rdatasets('A')]
ns.sort()
self.assertEqual(ns, [linkcheck.dns.name.from_text('ns1', None),
linkcheck.dns.name.from_text('ns2', None)])
def testIterateRdatas(self):
z = linkcheck.dns.zone.from_text(example_text, 'example.', relativize=True)
l = list(z.iterate_rdatas('A'))
l.sort()
exl = [(linkcheck.dns.name.from_text('ns1', None),
3600,
linkcheck.dns.rdata.from_text(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.A,
'10.0.0.1')),
(linkcheck.dns.name.from_text('ns2', None),
3600,
linkcheck.dns.rdata.from_text(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.A,
'10.0.0.2'))]
self.assertEqual(l, exl)
def testTTLs(self):
z = linkcheck.dns.zone.from_text(ttl_example_text, 'example.', relativize=True)
n = z['@']
rds = n.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.SOA)
self.assertEqual(rds.ttl, 3600)
n = z['ns1']
rds = n.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.A)
self.assertEqual(rds.ttl, 86401)
n = z['ns2']
rds = n.get_rdataset(linkcheck.dns.rdataclass.IN, linkcheck.dns.rdatatype.A)
self.assertEqual(rds.ttl, 694861)
def testNoSOA(self):
def bad():
z = linkcheck.dns.zone.from_text(no_soa_text, 'example.',
relativize=True)
self.assertRaises(linkcheck.dns.zone.NoSOA, bad)
def testNoNS(self):
def bad():
z = linkcheck.dns.zone.from_text(no_ns_text, 'example.',
relativize=True)
self.assertRaises(linkcheck.dns.zone.NoNS, bad)
def testInclude(self):
z1 = linkcheck.dns.zone.from_text(include_text, 'example.', relativize=True,
allow_include=True)
z2 = linkcheck.dns.zone.from_file(fname('example'), 'example.',
relativize=True)
self.assertEqual(z1, z2)
def testBadDirective(self):
def bad():
z = linkcheck.dns.zone.from_text(bad_directive_text, 'example.',
relativize=True)
self.assertRaises(linkcheck.dns.exception.SyntaxError, bad)
def test_suite ():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestZone))
return suite
if __name__ == '__main__':
unittest.main()

421
linkcheck/dns/tokenizer.py Normal file
View file

@ -0,0 +1,421 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Tokenize DNS master file format"""
import cStringIO
import sys
import linkcheck.dns.exception
import linkcheck.dns.name
_DELIMITERS = {
' ' : True,
'\t' : True,
'\n' : True,
';' : True,
'(' : True,
')' : True,
'"' : True }
_QUOTING_DELIMITERS = { '"' : True }
EOF = 0
EOL = 1
WHITESPACE = 2
IDENTIFIER = 3
QUOTED_STRING = 4
COMMENT = 5
DELIMITER = 6
class UngetBufferFull(linkcheck.dns.exception.DNSException):
"""Raised when an attempt is made to unget a token when the unget
buffer is full."""
pass
class Tokenizer(object):
"""A DNS master file format tokenizer.
A token is a (type, value) tuple, where I{type} is an int, and
I{value} is a string. The valid types are EOF, EOL, WHITESPACE,
IDENTIFIER, QUOTED_STRING, COMMENT, and DELIMITER.
@ivar file: The file to tokenize
@type file: file
@ivar ungotten_char: The most recently ungotten character, or None.
@type ungotten_char: string
@ivar ungotten_token: The most recently ungotten token, or None.
@type ungotten_token: (int, string) token tuple
@ivar multiline: The current multiline level. This value is increased
by one every time a '(' delimiter is read, and decreased by one every time
a ')' delimiter is read.
@type multiline: int
@ivar quoting: This variable is true if the tokenizer is currently
reading a quoted string.
@type quoting: bool
@ivar eof: This variable is true if the tokenizer has encountered EOF.
@type eof: bool
@ivar delimiters: The current delimiter dictionary.
@type delimiters: dict
@ivar line_number: The current line number
@type line_number: int
@ivar filename: A filename that will be returned by the L{where} method.
@type filename: string
"""
def __init__(self, f=sys.stdin, filename=None):
"""Initialize a tokenizer instance.
@param f: The file to tokenize. The default is sys.stdin.
This parameter may also be a string, in which case the tokenizer
will take its input from the contents of the string.
@type f: file or string
@param filename: the name of the filename that the L{where} method
will return.
@type filename: string
"""
if isinstance(f, str):
f = cStringIO.StringIO(f)
if filename is None:
filename = '<string>'
else:
if filename is None:
if f is sys.stdin:
filename = '<stdin>'
else:
filename = '<file>'
self.file = f
self.ungotten_char = None
self.ungotten_token = None
self.multiline = 0
self.quoting = False
self.eof = False
self.delimiters = _DELIMITERS
self.line_number = 1
self.filename = filename
def _get_char(self):
"""Read a character from input.
@rtype: string
"""
if self.ungotten_char is None:
if self.eof:
c = ''
else:
c = self.file.read(1)
if c == '':
self.eof = True
elif c == '\n':
self.line_number += 1
else:
c = self.ungotten_char
self.ungotten_char = None
return c
def where(self):
"""Return the current location in the input.
@rtype: (string, int) tuple. The first item is the filename of
the input, the second is the current line number.
"""
return (self.filename, self.line_number)
def _unget_char(self, c):
"""Unget a character.
The unget buffer for characters is only one character large; it is
an error to try to unget a character when the unget buffer is not
empty.
@param c: the character to unget
@type c: string
@raises UngetBufferFull: there is already an ungotten char
"""
if not self.ungotten_char is None:
raise UngetBufferFull
self.ungotten_char = c
def skip_whitespace(self):
"""Consume input until a non-whitespace character is encountered.
The non-whitespace character is then ungotten, and the number of
whitespace characters consumed is returned.
If the tokenizer is in multiline mode, then newlines are whitespace.
@rtype: int
"""
skipped = 0
while True:
c = self._get_char()
if c != ' ' and c != '\t':
if (c != '\n') or not self.multiline:
self._unget_char(c)
return skipped
skipped += 1
raise AssertionError("skip_whitespace() broke endless loop")
def get(self, want_leading = False, want_comment = False):
"""Get the next token.
@param want_leading: If True, return a WHITESPACE token if the
first character read is whitespace. The default is False.
@type want_leading: bool
@param want_comment: If True, return a COMMENT token if the
first token read is a comment. The default is False.
@type want_comment: bool
@rtype: (int, string) tuple
@raises linkcheck.dns.exception.UnexpectedEnd: input ended prematurely
@raises linkcheck.dns.exception.SyntaxError: input was badly formed
"""
if not self.ungotten_token is None:
token = self.ungotten_token
self.ungotten_token = None
if token[0] == WHITESPACE:
if want_leading:
return token
elif token[0] == COMMENT:
if want_comment:
return token
else:
return token
skipped = self.skip_whitespace()
if want_leading and skipped > 0:
return (WHITESPACE, ' ')
token = ''
ttype = IDENTIFIER
while True:
c = self._get_char()
if c == '' or c in self.delimiters:
if c == '' and self.quoting:
raise linkcheck.dns.exception.UnexpectedEnd
if token == '' and ttype != QUOTED_STRING:
if c == '(':
self.multiline += 1
self.skip_whitespace()
continue
elif c == ')':
if not self.multiline > 0:
raise linkcheck.dns.exception.SyntaxError
self.multiline -= 1
self.skip_whitespace()
continue
elif c == '"':
if not self.quoting:
self.quoting = True
self.delimiters = _QUOTING_DELIMITERS
ttype = QUOTED_STRING
continue
else:
self.quoting = False
self.delimiters = _DELIMITERS
self.skip_whitespace()
continue
elif c == '\n':
return (EOL, '\n')
elif c == ';':
while 1:
c = self._get_char()
if c == '\n' or c == '':
break
token += c
if want_comment:
self._unget_char(c)
return (COMMENT, token)
elif c == '':
if self.multiline:
raise linkcheck.dns.exception.SyntaxError, \
'unbalanced parentheses'
return (EOF, '')
elif self.multiline:
self.skip_whitespace()
token = ''
continue
else:
return (EOL, '\n')
else:
# This code exists in case we ever want a
# delimiter to be returned. It never produces
# a token currently.
token = c
ttype = DELIMITER
else:
self._unget_char(c)
break
elif self.quoting:
if c == '\\':
c = self._get_char()
if c == '':
raise linkcheck.dns.exception.UnexpectedEnd
if c.isdigit():
c2 = self._get_char()
if c2 == '':
raise linkcheck.dns.exception.UnexpectedEnd
c3 = self._get_char()
if c == '':
raise linkcheck.dns.exception.UnexpectedEnd
if not (c2.isdigit() and c3.isdigit()):
raise linkcheck.dns.exception.SyntaxError
c = chr(int(c) * 100 + int(c2) * 10 + int(c3))
elif c == '\n':
raise linkcheck.dns.exception.SyntaxError, 'newline in quoted string'
elif c == '\\':
#
# Treat \ followed by a delimiter as the
# delimiter, otherwise leave it alone.
#
c = self._get_char()
if c == '' or not c in self.delimiters:
self._unget_char(c)
c = '\\'
token += c
if token == '' and ttype != QUOTED_STRING:
if self.multiline:
raise linkcheck.dns.exception.SyntaxError, 'unbalanced parentheses'
ttype = EOF
return (ttype, token)
def unget(self, token):
"""Unget a token.
The unget buffer for tokens is only one token large; it is
an error to try to unget a token when the unget buffer is not
empty.
@param token: the token to unget
@type token: (int, string) token tuple
@raises UngetBufferFull: there is already an ungotten token
"""
if not self.ungotten_token is None:
raise UngetBufferFull
self.ungotten_token = token
def next(self):
"""Return the next item in an iteration.
@rtype: (int, string)
"""
token = self.get()
if token[0] == EOF:
raise StopIteration
return token
def __iter__(self):
return self
# Helpers
def get_int(self):
"""Read the next token and interpret it as an integer.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: int
"""
(ttype, value) = self.get()
if ttype != IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError, 'expecting an identifier'
if not value.isdigit():
raise linkcheck.dns.exception.SyntaxError, 'expecting an integer'
return int(value)
def get_uint8(self):
"""Read the next token and interpret it as an 8-bit unsigned
integer.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: int
"""
value = self.get_int()
if value < 0 or value > 255:
raise linkcheck.dns.exception.SyntaxError, \
'%d is not an unsigned 8-bit integer' % value
return value
def get_uint16(self):
"""Read the next token and interpret it as a 16-bit unsigned
integer.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: int
"""
value = self.get_int()
if value < 0 or value > 65535:
raise linkcheck.dns.exception.SyntaxError, \
'%d is not an unsigned 16-bit integer' % value
return value
def get_uint32(self):
"""Read the next token and interpret it as a 32-bit unsigned
integer.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: int
"""
(ttype, value) = self.get()
if ttype != IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError, 'expecting an identifier'
if not value.isdigit():
raise linkcheck.dns.exception.SyntaxError, 'expecting an integer'
value = long(value)
if value < 0 or value > 4294967296L:
raise linkcheck.dns.exception.SyntaxError, \
'%d is not an unsigned 32-bit integer' % value
return value
def get_string(self, origin=None):
"""Read the next token and interpret it as a string.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: string
"""
(ttype, t) = self.get()
if ttype != IDENTIFIER and ttype != QUOTED_STRING:
raise linkcheck.dns.exception.SyntaxError, 'expecting a string'
return t
def get_name(self, origin=None):
"""Read the next token and interpret it as a DNS name.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: linkcheck.dns.name.Name object"""
(ttype, t) = self.get()
if ttype != IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError, 'expecting an identifier'
return linkcheck.dns.name.from_text(t, origin)
def get_eol(self):
"""Read the next token and raise an exception if it isn't EOL or
EOF.
@raises linkcheck.dns.exception.SyntaxError:
@rtype: string
"""
(ttype, t) = self.get()
if ttype != EOL and ttype != EOF:
raise linkcheck.dns.exception.SyntaxError, \
'expected EOL or EOF, got %d "%s"' % (ttype, t)
return t

124
linkcheck/dns/tsig.py Normal file
View file

@ -0,0 +1,124 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2001-2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS TSIG support."""
import hmac
import struct
import linkcheck.dns.exception
import linkcheck.dns.rdataclass
import linkcheck.dns.name
class BadTime(linkcheck.dns.exception.DNSException):
"""Raised if the current time is not within the TSIG's validity time."""
pass
class BadSignature(linkcheck.dns.exception.DNSException):
"""Raised if the TSIG signature fails to verify."""
pass
_alg_name = linkcheck.dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT.').to_digestable()
def hmac_md5(wire, keyname, secret, time, fudge, original_id, error,
other_data, request_mac, ctx=None, multi=False, first=True):
"""Return a (tsig_rdata, mac, ctx) tuple containing the HMAC-MD5 TSIG rdata
for the input parameters, the HMAC-MD5 MAC calculated by applying the
TSIG signature algorithm, and the TSIG digest context.
@rtype: (string, string, hmac.HMAC object)
@raises ValueError: I{other_data} is too long
"""
if first:
ctx = hmac.new(secret)
ml = len(request_mac)
if ml > 0:
ctx.update(struct.pack('!H', ml))
ctx.update(request_mac)
id = struct.pack('!H', original_id)
ctx.update(id)
ctx.update(wire[2:])
if first:
ctx.update(keyname.to_digestable())
ctx.update(struct.pack('!H', linkcheck.dns.rdataclass.ANY))
ctx.update(struct.pack('!I', 0))
long_time = time + 0L
upper_time = (long_time >> 32) & 0xffffL
lower_time = long_time & 0xffffffffL
time_mac = struct.pack('!HIH', upper_time, lower_time, fudge)
pre_mac = _alg_name + time_mac
ol = len(other_data)
if ol > 65535:
raise ValueError, 'TSIG Other Data is > 65535 bytes'
post_mac = struct.pack('!HH', error, ol) + other_data
if first:
ctx.update(pre_mac)
ctx.update(post_mac)
else:
ctx.update(time_mac)
mac = ctx.digest()
mpack = struct.pack('!H', len(mac))
tsig_rdata = pre_mac + mpack + mac + id + post_mac
if multi:
ctx = hmac.new(secret)
ml = len(mac)
ctx.update(struct.pack('!H', ml))
ctx.update(mac)
else:
ctx = None
return (tsig_rdata, mac, ctx)
def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata,
tsig_rdlen, ctx=None, multi=False, first=True):
"""Validate the specified TSIG rdata against the other input parameters.
@raises FormError: The TSIG is badly formed.
@raises BadTime: There is too much time skew between the client and the
server.
@raises BadSignature: The TSIG signature did not validate
@rtype: hmac.HMAC object"""
(adcount,) = struct.unpack("!H", wire[10:12])
if adcount == 0:
raise linkcheck.dns.exception.FormError
adcount -= 1
new_wire = wire[0:10] + struct.pack("!H", adcount) + wire[12:tsig_start]
current = tsig_rdata
(aname, used) = linkcheck.dns.name.from_wire(wire, current)
current = current + used
(upper_time, lower_time, fudge, mac_size) = \
struct.unpack("!HIHH", wire[current:current + 10])
time = ((upper_time + 0L) << 32) + (lower_time + 0L)
current += 10
mac = wire[current:current + mac_size]
current += mac_size
(original_id, error, other_size) = \
struct.unpack("!HHH", wire[current:current + 6])
current += 6
other_data = wire[current:current + other_size]
current += other_size
if current != tsig_rdata + tsig_rdlen:
raise linkcheck.dns.exception.FormError
time_low = time - fudge
time_high = time + fudge
if now < time_low or now > time_high:
raise BadTime
(junk, our_mac, ctx) = hmac_md5(new_wire, keyname, secret, time, fudge,
original_id, error, other_data,
request_mac, ctx, multi, first)
if (our_mac != mac):
raise BadSignature
return ctx

View file

@ -0,0 +1,45 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""A place to store TSIG keys."""
import base64
import linkcheck.dns.name
def from_text(textring):
"""Convert a dictionary containing (textual DNS name, base64 secret) pairs
into a binary keyring which has (linkcheck.dns.name.Name, binary secret) pairs.
@rtype: dict"""
keyring = {}
for keytext in textring:
keyname = linkcheck.dns.name.from_text(keytext)
secret = base64.decodestring(textring[keytext])
keyring[keyname] = secret
return keyring
def to_text(keyring):
"""Convert a dictionary containing (linkcheck.dns.name.Name, binary secret) pairs
into a text keyring which has (textual DNS name, base64 secret) pairs.
@rtype: dict"""
textring = {}
for keyname in keyring:
keytext = linkcheck.dns.name.to_text(keyname)
secret = base64.encodestring(keyring[keyname])
textring[keytext] = secret
return textring

62
linkcheck/dns/ttl.py Normal file
View file

@ -0,0 +1,62 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS TTL conversion."""
import linkcheck.dns.exception
class BadTTL(linkcheck.dns.exception.SyntaxError):
pass
def from_text(text):
"""Convert the text form of a TTL to an integer.
The BIND 8 units syntax for TTLs (e.g. '1w6d4h3m10s') is supported.
@param text: the textual TTL
@type text: string
@raises linkcheck.dns.ttl.BadTTL: the TTL is not well-formed
@rtype: int
"""
if text.isdigit():
return int(text)
if not text[0].isdigit():
raise BadTTL
total = 0
current = 0
for c in text:
if c.isdigit():
current *= 10
current += int(c)
else:
c = c.lower()
if c == 'w':
total += current * 604800
elif c == 'd':
total += current * 86400
elif c == 'h':
total += current * 3600
elif c == 'm':
total += current * 60
elif c == 's':
total += current
else:
raise BadTTL, "unknown unit '%s'" % c
current = 0
if not current == 0:
raise BadTTL, "trailing integer"
return total

241
linkcheck/dns/update.py Normal file
View file

@ -0,0 +1,241 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Dynamic Update Support"""
import linkcheck.dns.message
import linkcheck.dns.name
import linkcheck.dns.opcode
import linkcheck.dns.rdata
import linkcheck.dns.rdataclass
import linkcheck.dns.rdataset
class Update(linkcheck.dns.message.Message):
def __init__(self, zone, rdclass=linkcheck.dns.rdataclass.IN, keyring=None,
keyname=None):
"""Initialize a new DNS Update object.
@param zone: The zone which is being updated.
@type zone: A linkcheck.dns.name.Name or string
@param rdclass: The class of the zone; defaults to linkcheck.dns.rdataclass.IN.
@type rdclass: An int designating the class, or a string whose value
is the name of a class.
@param keyring: The TSIG keyring to use; defaults to None.
@type keyring: dict
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@type keyname: linkcheck.dns.name.Name or string
"""
super(Update, self).__init__()
self.flags |= linkcheck.dns.opcode.to_flags(linkcheck.dns.opcode.UPDATE)
if isinstance(zone, str):
zone = linkcheck.dns.name.from_text(zone)
else:
zone = zone.copy()
self.origin = zone
if isinstance(rdclass, str):
rdclass = linkcheck.dns.rdataclass.from_text(rdclass)
self.zone_rdclass = rdclass
self.find_rrset(self.question, self.origin, rdclass, linkcheck.dns.rdatatype.SOA,
create=True, force_unique=True)
if not keyring is None:
self.use_tsig(keyring, keyname)
def _add_rr(self, name, ttl, rd, deleting=None, section=None):
"""Add a single RR to the update section."""
if section is None:
section = self.authority
covers = rd.covers()
rrset = self.find_rrset(section, name, self.zone_rdclass, rd.rdtype,
covers, deleting, True, True)
rrset.add(rd, ttl)
def _add(self, replace, section, name, *args):
"""Add records. The first argument is the replace mode. If
false, RRs are added to an existing RRset; if true, the RRset
is replaced with the specified contents. The second
argument is the section to add to. The third argument
is always a name. The other arguments can be:
- rdataset...
- ttl, rdata...
- ttl, rdtype, string..."""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if isinstance(args[0], linkcheck.dns.rdataset.Rdataset):
for rds in args:
if replace:
self.delete(name, rds.rdtype)
for rd in rds:
self._add_rr(name, rds.ttl, rd, section=section)
else:
args = list(args)
ttl = int(args.pop(0))
if isinstance(args[0], linkcheck.dns.rdata.Rdata):
if replace:
self.delete(name, args[0].rdtype)
for rd in args:
self._add_rr(name, ttl, rd, section=section)
else:
rdtype = args.pop(0)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if replace:
self.delete(name, rdtype)
for s in args:
rd = linkcheck.dns.rdata.from_text(self.zone_rdclass, rdtype, s,
self.origin)
self._add_rr(name, ttl, rd, section=section)
def add(self, name, *args):
"""Add records. The first argument is always a name. The other
arguments can be:
- rdataset...
- ttl, rdata...
- ttl, rdtype, string..."""
self._add(False, self.authority, name, *args)
def delete(self, name, *args):
"""Delete records. The first argument is always a name. The other
arguments can be:
- I{nothing}
- rdataset...
- rdata...
- rdtype, [string...]"""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if len(args) == 0:
rrset = self.find_rrset(self.authority, name, linkcheck.dns.rdataclass.ANY,
linkcheck.dns.rdatatype.ANY, linkcheck.dns.rdatatype.NONE,
linkcheck.dns.rdatatype.ANY, True, True)
elif isinstance(args[0], linkcheck.dns.rdataset.Rdataset):
for rds in args:
for rd in rds:
self._add_rr(name, 0, rd, linkcheck.dns.rdataclass.NONE)
else:
args = list(args)
if isinstance(args[0], linkcheck.dns.rdata.Rdata):
for rd in args:
self._add_rr(name, 0, rd, linkcheck.dns.rdataclass.NONE)
else:
rdtype = args.pop(0)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if len(args) == 0:
rrset = self.find_rrset(self.authority, name,
self.zone_rdclass, rdtype,
linkcheck.dns.rdatatype.NONE,
linkcheck.dns.rdataclass.ANY,
True, True)
else:
for s in args:
rd = linkcheck.dns.rdata.from_text(self.zone_rdclass, rdtype, s,
self.origin)
self._add_rr(name, 0, rd, linkcheck.dns.rdataclass.NONE)
def replace(self, name, *args):
"""Replace records. The first argument is always a name. The other
arguments can be:
- rdataset...
- ttl, rdata...
- ttl, rdtype, string...
Note that if you want to replace the entire node, you should do
a delete of the name followed by one or more calls to add."""
self._add(True, self.authority, name, *args)
def present(self, name, *args):
"""Require that an owner name (and optionally an rdata type,
or specific rdataset) exists as a prerequisite to the
execution of the update. The first argument is always a name.
The other arguments can be:
- rdataset...
- rdata...
- rdtype, string..."""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if len(args) == 0:
rrset = self.find_rrset(self.answer, name,
linkcheck.dns.rdataclass.ANY, linkcheck.dns.rdatatype.ANY,
linkcheck.dns.rdatatype.NONE, None,
True, True)
elif isinstance(args[0], linkcheck.dns.rdataset.Rdataset) or \
isinstance(args[0], linkcheck.dns.rdata.Rdata) or \
len(args) > 1:
if len(args) > 1:
# Add a 0 TTL
args = list(args)
args.insert(0, 0)
self._add(False, self.answer, name, *args)
else:
rdtype = args[0]
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
rrset = self.find_rrset(self.answer, name,
linkcheck.dns.rdataclass.ANY, rdtype,
linkcheck.dns.rdatatype.NONE, None,
True, True)
def absent(self, name, rdtype=None):
"""Require that an owner name (and optionally an rdata type) does
not exist as a prerequisite to the execution of the update."""
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
if rdtype is None:
rrset = self.find_rrset(self.answer, name,
linkcheck.dns.rdataclass.NONE, linkcheck.dns.rdatatype.ANY,
linkcheck.dns.rdatatype.NONE, None,
True, True)
else:
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
rrset = self.find_rrset(self.answer, name,
linkcheck.dns.rdataclass.NONE, rdtype,
linkcheck.dns.rdatatype.NONE, None,
True, True)
def to_wire(self, origin=None, max_size=65535, **kw):
"""Return a string containing the update in DNS compressed wire
format.
@rtype: string"""
if origin is None:
origin = self.origin
return super(Update, self).to_wire(origin, max_size)

35
linkcheck/dns/version.py Normal file
View file

@ -0,0 +1,35 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""dnspython release version information."""
MAJOR = 1
MINOR = 3
MICRO = 2
RELEASELEVEL = 0x0f
SERIAL = 0
if RELEASELEVEL == 0x0f:
version = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
elif RELEASELEVEL == 0x00:
version = '%d.%d.%dx%d' % \
(MAJOR, MINOR, MICRO, SERIAL)
else:
version = '%d.%d.%d%x%d' % \
(MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL)
hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | \
SERIAL

833
linkcheck/dns/zone.py Normal file
View file

@ -0,0 +1,833 @@
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2003, 2004 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Zones."""
from __future__ import generators
import sys
import linkcheck.dns.exception
import linkcheck.dns.name
import linkcheck.dns.node
import linkcheck.dns.rdataclass
import linkcheck.dns.rdatatype
import linkcheck.dns.rdata
import linkcheck.dns.tokenizer
import linkcheck.dns.ttl
class BadZone(linkcheck.dns.exception.DNSException):
"""The zone is malformed."""
pass
class NoSOA(BadZone):
"""The zone has no SOA RR at its origin."""
pass
class NoNS(BadZone):
"""The zone has no NS RRset at its origin."""
pass
class Zone(object):
"""A DNS zone.
A Zone is a mapping from names to nodes. The zone object may be
treated like a Python dictionary, e.g. zone[name] will retrieve
the node associated with that name. The I{name} may be a
linkcheck.dns.name.Name object, or it may be a string. In the either case,
if the name is relative it is treated as relative to the origin of
the zone.
@ivar rdclass: The zone's rdata class; the default is class IN.
@type rdclass: int
@ivar origin: The origin of the zone.
@type origin: linkcheck.dns.name.Name object
@ivar nodes: A dictionary mapping the names of nodes in the zone to the
nodes themselves.
@type nodes: dict
@ivar relativize: should names in the zone be relativized?
@type relativize: bool
@cvar node_factory: the factory used to create a new node
@type node_factory: class or callable
"""
node_factory = linkcheck.dns.node.Node
__slots__ = ['rdclass', 'origin', 'nodes', 'relativize']
def __init__(self, origin, rdclass=linkcheck.dns.rdataclass.IN, relativize=True):
"""Initialize a zone object.
@param origin: The origin of the zone.
@type origin: linkcheck.dns.name.Name object
@param rdclass: The zone's rdata class; the default is class IN.
@type rdclass: int"""
self.rdclass = rdclass
self.origin = origin
self.nodes = {}
self.relativize = relativize
def __eq__(self, other):
"""Two zones are equal if they have the same origin, class, and
nodes.
@rtype: bool
"""
if not isinstance(other, Zone):
return False
if self.rdclass != other.rdclass or \
self.origin != other.origin or \
self.nodes != other.nodes:
return False
return True
def __ne__(self, other):
"""Are two zones not equal?
@rtype: bool
"""
return not self.__eq__(other)
def _validate_name(self, name):
if isinstance(name, str):
name = linkcheck.dns.name.from_text(name, None)
elif not isinstance(name, linkcheck.dns.name.Name):
raise KeyError, \
"name parameter must be convertable to a DNS name"
if name.is_absolute():
if not name.is_subdomain(self.origin):
raise KeyError, \
"name parameter must be a subdomain of the zone origin"
if self.relativize:
name = name.relativize(self.origin)
return name
def __getitem__(self, key):
key = self._validate_name(key)
return self.nodes[key]
def __setitem__(self, key, value):
key = self._validate_name(key)
self.nodes[key] = value
def __delitem__(self, key):
key = self._validate_name(key)
del self.nodes[key]
def __iter__(self):
return self.nodes.iterkeys()
def iterkeys(self):
return self.nodes.iterkeys()
def keys(self):
return self.nodes.keys()
def itervalues(self):
return self.nodes.itervalues()
def values(self):
return self.nodes.values()
def iteritems(self):
return self.nodes.iteritems()
def items(self):
return self.nodes.items()
def get(self, key):
key = self._validate_name(key)
return self.nodes.get(key)
def __contains__(self, other):
return other in self.nodes
def find_node(self, name, create=False):
"""Find a node in the zone, possibly creating it.
@param name: the name of the node to find
@type name: linkcheck.dns.name.Name object or string
@param create: should the node be created if it doesn't exist?
@type create: bool
@raises KeyError: the name is not known and create was not specified.
@rtype linkcheck.dns.node.Node object
"""
name = self._validate_name(name)
node = self.nodes.get(name)
if node is None:
if not create:
raise KeyError
node = self.node_factory()
self.nodes[name] = node
return node
def get_node(self, name, create=False):
"""Get a node in the zone, possibly creating it.
This method is like L{find_node}, except it returns None instead
of raising an exception if the node does not exist and creation
has not been requested.
@param name: the name of the node to find
@type name: linkcheck.dns.name.Name object or string
@param create: should the node be created if it doesn't exist?
@type create: bool
@rtype linkcheck.dns.node.Node object or None
"""
try:
node = self.find_node(name, create)
except KeyError:
node = None
return node
def delete_node(self, name):
"""Delete the specified node if it exists.
It is not an error if the node does not exist.
"""
name = self._validate_name(name)
if self.nodes.has_key(name):
del self.nodes[name]
def find_rdataset(self, name, rdtype, covers=linkcheck.dns.rdatatype.NONE,
create=False):
"""Look for rdata with the specified name and type in the zone,
and return an rdataset encapsulating it.
The I{name}, I{rdtype}, and I{covers} parameters may be
strings, in which case they will be converted to their proper
type.
The rdataset returned is not a copy; changes to it will change
the zone.
KeyError is raised if the name or type are not found.
Use L{get_rdataset} if you want to have None returned instead.
@param name: the owner name to look for
@type name: DNS.name.Name object or string
@param rdtype: the rdata type desired
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
@param create: should the node and rdataset be created if they do not
exist?
@type create: bool
@raises KeyError: the node or rdata could not be found
@rtype: linkcheck.dns.rrset.RRset object
"""
name = self._validate_name(name)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(covers, str):
covers = linkcheck.dns.rdatatype.from_text(covers)
node = self.find_node(name, create)
return node.find_rdataset(self.rdclass, rdtype, covers, create)
def get_rdataset(self, name, rdtype, covers=linkcheck.dns.rdatatype.NONE,
create=False):
"""Look for rdata with the specified name and type in the zone,
and return an rdataset encapsulating it.
The I{name}, I{rdtype}, and I{covers} parameters may be
strings, in which case they will be converted to their proper
type.
The rdataset returned is not a copy; changes to it will change
the zone.
None is returned if the name or type are not found.
Use L{find_rdataset} if you want to have KeyError raised instead.
@param name: the owner name to look for
@type name: DNS.name.Name object or string
@param rdtype: the rdata type desired
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
@param create: should the node and rdataset be created if they do not
exist?
@type create: bool
@rtype: linkcheck.dns.rrset.RRset object
"""
try:
rdataset = self.find_rdataset(name, rdtype, covers, create)
except KeyError:
rdataset = None
return rdataset
def delete_rdataset(self, name, rdtype, covers=linkcheck.dns.rdatatype.NONE):
"""Delete the rdataset matching I{rdtype} and I{covers}, if it
exists at the node specified by I{name}.
The I{name}, I{rdtype}, and I{covers} parameters may be
strings, in which case they will be converted to their proper
type.
It is not an error if the node does not exist, or if there is no
matching rdataset at the node.
If the node has no rdatasets after the deletion, it will itself
be deleted.
@param name: the owner name to look for
@type name: DNS.name.Name object or string
@param rdtype: the rdata type desired
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
"""
name = self._validate_name(name)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(covers, str):
covers = linkcheck.dns.rdatatype.from_text(covers)
node = self.get_node(name)
if not node is None:
node.delete_rdataset(self.rdclass, rdtype, covers)
if len(node) == 0:
self.delete_node(name)
def replace_rdataset(self, name, replacement):
"""Replace an rdataset at name.
It is not an error if there is no rdataset matching I{replacement}.
Ownership of the I{replacement} object is transferred to the zone;
in other words, this method does not store a copy of I{replacement}
at the node, it stores I{replacement} itself.
If the I{name} node does not exist, it is created.
@param name: the owner name
@type name: DNS.name.Name object or string
@param replacement: the replacement rdataset
@type replacement: linkcheck.dns.rdataset.Rdataset
"""
if replacement.rdclass != self.rdclass:
raise ValueError, 'replacement.rdclass != zone.rdclass'
node = self.find_node(name, True)
node.replace_rdataset(replacement)
def find_rrset(self, name, rdtype, covers=linkcheck.dns.rdatatype.NONE):
"""Look for rdata with the specified name and type in the zone,
and return an RRset encapsulating it.
The I{name}, I{rdtype}, and I{covers} parameters may be
strings, in which case they will be converted to their proper
type.
This method is less efficient than the similar
L{find_rdataset} because it creates an RRset instead of
returning the matching rdataset. It may be more convenient
for some uses since it returns an object which binds the owner
name to the rdata.
This method may not be used to create new nodes or rdatasets;
use L{find_rdataset} instead.
KeyError is raised if the name or type are not found.
Use L{get_rrset} if you want to have None returned instead.
@param name: the owner name to look for
@type name: DNS.name.Name object or string
@param rdtype: the rdata type desired
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
@raises KeyError: the node or rdata could not be found
@rtype: linkcheck.dns.rrset.RRset object
"""
name = self._validate_name(name)
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(covers, str):
covers = linkcheck.dns.rdatatype.from_text(covers)
rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers)
rrset = linkcheck.dns.rrset.RRset(name, self.rdclass, rdtype, covers)
rrset.update(rdataset)
return rrset
def get_rrset(self, name, rdtype, covers=linkcheck.dns.rdatatype.NONE):
"""Look for rdata with the specified name and type in the zone,
and return an RRset encapsulating it.
The I{name}, I{rdtype}, and I{covers} parameters may be
strings, in which case they will be converted to their proper
type.
This method is less efficient than the similar L{get_rdataset}
because it creates an RRset instead of returning the matching
rdataset. It may be more convenient for some uses since it
returns an object which binds the owner name to the rdata.
This method may not be used to create new nodes or rdatasets;
use L{find_rdataset} instead.
None is returned if the name or type are not found.
Use L{find_rrset} if you want to have KeyError raised instead.
@param name: the owner name to look for
@type name: DNS.name.Name object or string
@param rdtype: the rdata type desired
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
@rtype: linkcheck.dns.rrset.RRset object
"""
try:
rrset = self.find_rrset(name, rdtype, covers)
except KeyError:
rrset = None
return rrset
def iterate_rdatasets(self, rdtype=None, covers=linkcheck.dns.rdatatype.NONE):
"""Return a generator which yields (name, rdataset) tuples for
all rdatasets in the zone which have the specified I{rdtype}
and I{covers}. If I{rdtype} is linkcheck.dns.rdatatype.ANY, the default,
then all rdatasets will be matched.
@param rdtype: int or string
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
"""
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(covers, str):
covers = linkcheck.dns.rdatatype.from_text(covers)
for (name, node) in self.iteritems():
for rds in node:
if rdtype == linkcheck.dns.rdatatype.ANY or \
(rds.rdtype == rdtype and rds.covers == covers):
yield (name, rds)
def iterate_rdatas(self, rdtype=None, covers=linkcheck.dns.rdatatype.NONE):
"""Return a generator which yields (name, ttl, rdata) tuples for
all rdatas in the zone which have the specified I{rdtype}
and I{covers}. If I{rdtype} is linkcheck.dns.rdatatype.ANY, the default,
then all rdatas will be matched.
@param rdtype: int or string
@type rdtype: int or string
@param covers: the covered type (defaults to None)
@type covers: int or string
"""
if isinstance(rdtype, str):
rdtype = linkcheck.dns.rdatatype.from_text(rdtype)
if isinstance(covers, str):
covers = linkcheck.dns.rdatatype.from_text(covers)
for (name, node) in self.iteritems():
for rds in node:
if rdtype == linkcheck.dns.rdatatype.ANY or \
(rds.rdtype == rdtype and rds.covers == covers):
for rdata in rds:
yield (name, rds.ttl, rdata)
def to_file(self, f, sorted=True, relativize=True, nl=None):
"""Write a zone to a file.
@param f: file or string. If I{f} is a string, it is treated
as the name of a file to open.
@param sorted: if True, the file will be written with the
names sorted in DNSSEC order from least to greatest. Otherwise
the names will be written in whatever order they happen to have
in the zone's dictionary.
@param relativize: if True, domain names in the output will be
relativized to the zone's origin (if possible).
@type relativize: bool
@param nl: The end of line string. If not specified, the
output will use the platform's native end-of-line marker (i.e.
LF on POSIX, CRLF on Windows, CR on Macintosh).
@type nl: string or None
"""
if sys.hexversion >= 0x02030000:
# allow Unicode filenames
str_type = basestring
else:
str_type = str
if nl is None:
opts = 'w'
else:
opts = 'wb'
if isinstance(f, str_type):
f = file(f, opts)
want_close = True
else:
want_close = False
try:
if sorted:
names = self.keys()
names.sort()
else:
names = self.iterkeys()
for n in names:
l = self[n].to_text(n, origin=self.origin,
relativize=relativize)
if nl is None:
print >> f, l
else:
f.write(l)
f.write(nl)
finally:
if want_close:
f.close()
def check_origin(self):
"""Do some simple checking of the zone's origin.
@raises linkcheck.dns.zone.NoSOA: there is no SOA RR
@raises linkcheck.dns.zone.NoNS: there is no NS RRset
@raises KeyError: there is no origin node
"""
if self.relativize:
name = linkcheck.dns.name.empty
else:
name = self.origin
if self.get_rdataset(name, linkcheck.dns.rdatatype.SOA) is None:
raise NoSOA
if self.get_rdataset(name, linkcheck.dns.rdatatype.NS) is None:
raise NoNS
class _MasterReader(object):
"""Read a DNS master file
@ivar tok: The tokenizer
@type tok: linkcheck.dns.tokenizer.Tokenizer object
@ivar ttl: The default TTL
@type ttl: int
@ivar last_name: The last name read
@type last_name: linkcheck.dns.name.Name object
@ivar current_origin: The current origin
@type current_origin: linkcheck.dns.name.Name object
@ivar relativize: should names in the zone be relativized?
@type relativize: bool
@ivar zone: the zone
@type zone: linkcheck.dns.zone.Zone object
@ivar saved_state: saved reader state (used when processing $INCLUDE)
@type saved_state: list of (tokenizer, current_origin, last_name, file)
tuples.
@ivar current_file: the file object of the $INCLUDed file being parsed
(None if no $INCLUDE is active).
@ivar allow_include: is $INCLUDE allowed?
@type allow_include: bool
"""
def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
allow_include=False):
if isinstance(origin, str):
origin = linkcheck.dns.name.from_text(origin)
self.tok = tok
self.current_origin = origin
self.relativize = relativize
self.ttl = 0
self.last_name = None
self.zone = zone_factory(origin, rdclass, relativize=relativize)
self.saved_state = []
self.current_file = None
self.allow_include = allow_include
def _eat_line(self):
while 1:
(ttype, t) = self.tok.get()
if ttype == linkcheck.dns.tokenizer.EOL or ttype == linkcheck.dns.tokenizer.EOF:
break
def _rr_line(self):
"""Process one line from a DNS master file."""
# Name
token = self.tok.get(want_leading = True)
if token[0] != linkcheck.dns.tokenizer.WHITESPACE:
self.last_name = linkcheck.dns.name.from_text(token[1], self.current_origin)
else:
token = self.tok.get()
if token[0] == linkcheck.dns.tokenizer.EOL or \
token[0] == linkcheck.dns.tokenizer.EOF:
# treat leading WS followed by EOL/EOF as if they were EOL/EOF.
return
self.tok.unget(token)
name = self.last_name
if not name.is_subdomain(self.zone.origin):
self._eat_line()
return
if self.relativize:
name = name.relativize(self.zone.origin)
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
# TTL
try:
ttl = linkcheck.dns.ttl.from_text(token[1])
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
except linkcheck.dns.ttl.BadTTL:
ttl = self.ttl
# Class
try:
rdclass = linkcheck.dns.rdataclass.from_text(token[1])
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError
except linkcheck.dns.exception.SyntaxError:
raise linkcheck.dns.exception.SyntaxError
except:
rdclass = self.zone.rdclass
if rdclass != self.zone.rdclass:
raise linkcheck.dns.exception.SyntaxError, "RR class is not zone's class"
# Type
try:
rdtype = linkcheck.dns.rdatatype.from_text(token[1])
except:
raise linkcheck.dns.exception.SyntaxError, \
"unknown rdatatype '%s'" % token[1]
n = self.zone.nodes.get(name)
if n is None:
n = self.zone.node_factory()
self.zone.nodes[name] = n
try:
rd = linkcheck.dns.rdata.from_text(rdclass, rdtype, self.tok,
self.current_origin, False)
except linkcheck.dns.exception.SyntaxError:
# Catch and reraise.
(ty, va) = sys.exc_info()[:2]
raise ty, va
except:
# All exceptions that occur in the processing of rdata
# are treated as syntax errors. This is not strictly
# correct, but it is correct almost all of the time.
# We convert them to syntax errors so that we can emit
# helpful filename:line info.
(ty, va) = sys.exc_info()[:2]
raise linkcheck.dns.exception.SyntaxError, \
"caught exception %s: %s" % (str(ty), str(va))
rd.choose_relativity(self.zone.origin, self.relativize)
covers = rd.covers()
rds = n.find_rdataset(rdclass, rdtype, covers, True)
rds.add(rd, ttl)
def read(self):
"""Read a DNS master file and build a zone object.
@raises linkcheck.dns.zone.NoSOA: No SOA RR was found at the zone origin
@raises linkcheck.dns.zone.NoNS: No NS RRset was found at the zone origin
"""
try:
while 1:
token = self.tok.get(True, True)
if token[0] == linkcheck.dns.tokenizer.EOF:
if not self.current_file is None:
self.current_file.close()
if len(self.saved_state) > 0:
(self.tok,
self.current_origin,
self.last_name,
self.current_file,
self.ttl) = self.saved_state.pop(-1)
continue
break
elif token[0] == linkcheck.dns.tokenizer.EOL:
continue
elif token[0] == linkcheck.dns.tokenizer.COMMENT:
self.tok.get_eol()
continue
elif token[1][0] == '$':
u = token[1].upper()
if u == '$TTL':
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.IDENTIFIER:
raise linkcheck.dns.exception.SyntaxError, "bad $TTL"
self.ttl = linkcheck.dns.ttl.from_text(token[1])
self.tok.get_eol()
elif u == '$ORIGIN':
self.current_origin = self.tok.get_name()
self.tok.get_eol()
elif u == '$INCLUDE' and self.allow_include:
token = self.tok.get()
if token[0] != linkcheck.dns.tokenizer.QUOTED_STRING:
raise linkcheck.dns.exception.SyntaxError, \
"bad filename in $INCLUDE"
filename = token[1]
token = self.tok.get()
if token[0] == linkcheck.dns.tokenizer.IDENTIFIER:
new_origin = linkcheck.dns.name.from_text(token[1], \
self.current_origin)
self.tok.get_eol()
elif token[0] != linkcheck.dns.tokenizer.EOL and \
token[0] != linkcheck.dns.tokenizer.EOF:
raise linkcheck.dns.exception.SyntaxError, \
"bad origin in $INCLUDE"
else:
new_origin = self.current_origin
self.saved_state.append((self.tok,
self.current_origin,
self.last_name,
self.current_file,
self.ttl))
self.current_file = file(filename, 'r')
self.tok = linkcheck.dns.tokenizer.Tokenizer(self.current_file,
filename)
self.current_origin = new_origin
else:
raise linkcheck.dns.exception.SyntaxError, \
"Unknown master file directive '" + u + "'"
continue
self.tok.unget(token)
self._rr_line()
except linkcheck.dns.exception.SyntaxError, detail:
(filename, line_number) = self.tok.where()
if detail is None:
detail = "syntax error"
raise linkcheck.dns.exception.SyntaxError, \
"%s:%d: %s" % (filename, line_number, detail)
# Now that we're done reading, do some basic checking of the zone.
self.zone.check_origin()
def from_text(text, origin, rdclass = linkcheck.dns.rdataclass.IN, relativize = True,
zone_factory=Zone, filename=None, allow_include=False):
"""Build a zone object from a master file format string.
@param text: the master file format input
@type text: string.
@param origin: The origin of the zone.
@type origin: linkcheck.dns.name.Name object or string
@param rdclass: The zone's rdata class; the default is class IN.
@type rdclass: int
@param relativize: should names be relativized? The default is True
@type relativize: bool
@param zone_factory: The zone factory to use
@type zone_factory: function returning a Zone
@param filename: The filename to emit when describing where an error
occurred; the default is '<string>'.
@param allow_include: is $INCLUDE allowed?
@type allow_include: bool
@type filename: string
@raises linkcheck.dns.zone.NoSOA: No SOA RR was found at the zone origin
@raises linkcheck.dns.zone.NoNS: No NS RRset was found at the zone origin
@rtype: linkcheck.dns.zone.Zone object
"""
# 'text' can also be a file, but we don't publish that fact
# since it's an implementation detail. The official file
# interface is from_file().
if filename is None:
filename = '<string>'
tok = linkcheck.dns.tokenizer.Tokenizer(text, filename)
reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory,
allow_include=allow_include)
reader.read()
return reader.zone
def from_file(f, origin, rdclass = linkcheck.dns.rdataclass.IN, relativize = True,
zone_factory=Zone, filename=None, allow_include=True):
"""Read a master file and build a zone object.
@param f: file or string. If I{f} is a string, it is treated
as the name of a file to open.
@param origin: The origin of the zone.
@type origin: linkcheck.dns.name.Name object or string
@param rdclass: The zone's rdata class; the default is class IN.
@type rdclass: int
@param relativize: should names be relativized? The default is True
@type relativize: bool
@param zone_factory: The zone factory to use
@type zone_factory: function returning a Zone
@param filename: The filename to emit when describing where an error
occurred; the default is '<file>', or the value of I{f} if I{f} is a
string.
@type filename: string
@param allow_include: is $INCLUDE allowed?
@type allow_include: bool
@raises linkcheck.dns.zone.NoSOA: No SOA RR was found at the zone origin
@raises linkcheck.dns.zone.NoNS: No NS RRset was found at the zone origin
@rtype: linkcheck.dns.zone.Zone object
"""
if sys.hexversion >= 0x02030000:
# allow Unicode filenames; turn on universal newline support
str_type = basestring
opts = 'rU'
else:
str_type = str
opts = 'r'
if isinstance(f, str_type):
if filename is None:
filename = f
f = file(f, opts)
want_close = True
else:
if filename is None:
filename = '<file>'
want_close = False
try:
z = from_text(f, origin, rdclass, relativize, zone_factory,
filename, allow_include)
finally:
if want_close:
f.close()
return z
def from_xfr(xfr, zone_factory=Zone, relativize=True):
"""Convert the output of a zone transfer generator into a zone object.
@param xfr: The xfr generator
@type xfr: generator of linkcheck.dns.message.Message objects
@param relativize: should names be relativized? The default is True
@type relativize: bool
@raises linkcheck.dns.zone.NoSOA: No SOA RR was found at the zone origin
@raises linkcheck.dns.zone.NoNS: No NS RRset was found at the zone origin
@rtype: linkcheck.dns.zone.Zone object
"""
z = None
for r in xfr:
if z is None:
origin = r.answer[0].name
rdclass = r.answer[0].rdclass
z = zone_factory(origin, rdclass, relativize=relativize)
for rrset in r.answer:
znode = z.nodes.get(rrset.name)
if not znode:
znode = z.node_factory()
z.nodes[rrset.name] = znode
zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype,
rrset.covers, True)
zrds.update_ttl(rrset.ttl)
for rd in rrset:
rd.choose_relativity(z.origin, relativize)
zrds.add(rd)
z.check_origin()
return z

Some files were not shown because too many files have changed in this diff Show more