1#!@PYTHON@
2
3# Copyright (C) 2010-2021 Internet Systems Consortium, Inc. ("ISC")
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0. If a copy of the MPL was not distributed with this
7# file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9"""
10Generator of various types of DNS data in the hex format.
11
12This script reads a human readable specification file (called "spec
13file" hereafter) that defines some type of DNS data (an RDATA, an RR,
14or a complete message) and dumps the defined data to a separate file
15as a "wire format" sequence parsable by the
16UnitTestUtil::readWireData() function (currently defined as part of
17libdns++ tests).  Many DNS related tests involve wire format test
18data, so it will be convenient if we can define the data in a more
19intuitive way than writing the entire hex sequence by hand.
20
21Here is a simple example.  Consider the following spec file:
22
23  [custom]
24  sections: a
25  [a]
26  as_rr: True
27
28When the script reads this file, it detects the file specifies a single
29component (called "section" here) that consists of a single A RDATA,
30which must be dumped as an RR (not only the part of RDATA).  It then
31dumps the following content:
32
33  # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=4)
34  076578616d706c6503636f6d00 0001 0001 00015180 0004
35  # Address=192.0.2.1
36  c0000201
37
38As can be seen, the script automatically completes all variable
39parameters of RRs: owner name, class, TTL, RDATA length and data.  For
40testing purposes many of these will be the same common one (like
41"example.com" or 192.0.2.1), so it would be convenient if we only have
42to specify non default parameters.  To change the RDATA (i.e., the
43IPv4 address), we should add the following line at the end of the spec
44file:
45
46  address: 192.0.2.2
47
48Then the last two lines of the output file will be as follows:
49
50  # Address=192.0.2.2
51  c0000202
52
53In some cases we would rather specify malformed data for tests.  This
54script has the ability to specify broken parameters for many types of
55data.  For example, we can generate data that would look like an A RR
56but the RDLEN is 3 by adding the following line to the spec file:
57
58  rdlen: 3
59
60Then the first two lines of the output file will be as follows:
61
62  # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=3)
63  076578616d706c6503636f6d00 0001 0001 00015180 0003
64
65** USAGE **
66
67  gen_wiredata.py [-o output_file] spec_file
68
69If the -o option is missing, and if the spec_file has a suffix (such as
70in the form of "data.spec"), the output file name will be the prefix
71part of it (as in "data"); if -o is missing and the spec_file does not
72have a suffix, the script will fail.
73
74** SPEC FILE SYNTAX **
75
76A spec file accepted in this script should be in the form of a
77configuration file that is parsable by the Python's standard
78configparser module.  In short, it consists of sections; each section
79is identified in the form of [section_name] followed by "name: value"
80entries.  Lines beginning with # or ; will be treated as comments.
81Refer to the configparser module documentation for further details of
82the general syntax.
83
84This script has two major modes: the custom mode and the DNS query
85mode.  The former generates an arbitrary combination of DNS message
86header, question section, RDATAs or RRs.  It is mainly intended to
87generate a test data for a single type of RDATA or RR, or for
88complicated complete DNS messages.  The DNS query mode is actually a
89special case of the custom mode, which is a shortcut to generate a
90simple DNS query message (with or without EDNS).
91
92* Custom mode syntax *
93
94By default this script assumes the DNS query mode.  To specify the
95custom mode, there must be a special "custom" section in the spec
96file, which should contain 'sections' entry.  This value of this
97entryis colon-separated string fields, each of which is either
98"header", "question", "edns", "name", or a string specifying an RR
99type.  For RR types the string is lower-cased string mnemonic that
100identifies the type: 'a' for type A, 'ns' for type NS, and so on
101(note: in the current implementation it's case sensitive, and must be
102lower cased).
103
104Each of these fields is interpreted as a section name of the spec
105(configuration), and in that section parameters specific to the
106semantics of the field can be configured.
107
108A "header" section specifies the content of a DNS message header.
109See the documentation of the DNSHeader class of this module for
110configurable parameters.
111
112A "question" section specifies the content of a single question that
113is normally to be placed in the Question section of a DNS message.
114See the documentation of the DNSQuestion class of this module for
115configurable parameters.
116
117An "edns" section specifies the content of an EDNS OPT RR.  See the
118documentation of the EDNS class of this module for configurable
119parameters.
120
121A "name" section specifies a domain name with or without compression.
122This is specifically intended to be used for testing name related
123functionalities and would rarely be used with other sections.  See the
124documentation of the Name class of this module for configurable
125parameters.
126
127In a specific section for an RR or RDATA, possible entries depend on
128the type.  But there are some common configurable entries.  See the
129description of the RR class.  The most important one would be "as_rr".
130It controls whether the entry should be treated as an RR (with name,
131type, class and TTL) or only as an RDATA.  By default as_rr is
132"False", so if an entry is to be interpreted as an RR, an as_rr entry
133must be explicitly specified with a value of "True".
134
135Another common entry is "rdlen".  It specifies the RDLEN field value
136of the RR (note: this is included when the entry is interpreted as
137RDATA, too).  By default this value is automatically determined by the
138RR type and (it has a variable length) from other fields of RDATA, but
139as shown in the above example, it can be explicitly set, possibly to a
140bogus value for testing against invalid data.
141
142For type specific entries (and their defaults when provided), see the
143documentation of the corresponding Python class defined in this
144module.  In general, there should be a class named the same mnemonic
145of the corresponding RR type for each supported type, and they are a
146subclass of the RR class.  For example, the "NS" class is defined for
147RR type NS.
148
149Look again at the A RR example shown at the beginning of this
150description.  There's a "custom" section, which consists of a
151"sections" entry whose value is a single "a", which means the data to
152be generated is an A RR or RDATA.  There's a corresponding "a"
153section, which only specifies that it should be interpreted as an RR
154(all field values of the RR are derived from the default).
155
156If you want to generate a data sequence for two ore more RRs or
157RDATAs, you can specify them in the form of colon-separated fields for
158the "sections" entry.  For example, to generate a sequence of A and NS
159RRs in that order, the "custom" section would be something like this:
160
161  [custom]
162  sections: a:ns
163
164and there must be an "ns" section in addition to "a".
165
166If a sequence of two or more RRs/RDATAs of the same RR type should be
167generated, these should be uniquely indexed with the "/" separator.
168For example, to generate two A RRs, the "custom" section would be as
169follows:
170
171  [custom]
172  sections: a/1:a/2
173
174and there must be "a/1" and "a/2" sections.
175
176Another practical example that would be used for many tests is to
177generate data for a complete DNS response message.  The spec file of
178such an example configuration would look like as follows:
179
180  [custom]
181  sections: header:question:a
182  [header]
183  qr: 1
184  ancount: 1
185  [question]
186  [a]
187  as_rr: True
188
189With this configuration, this script will generate test data for a DNS
190response to a query for example.com/IN/A containing one corresponding
191A RR in the answer section.
192
193* DNS query mode syntax *
194
195If the spec file does not contain a "custom" section (that has a
196"sections" entry), this script assumes the DNS query mode.  This mode
197is actually a special case of custom mode; it implicitly assumes the
198"sections" entry whose value is "header:question:edns".
199
200In this mode it is expected that the spec file also contains at least
201a "header" and "question" sections, and optionally an "edns" section.
202But the script does not warn or fail even if the expected sections are
203missing.
204
205* Entry value types *
206
207As described above, a section of the spec file accepts entries
208specific to the semantics of the section.  They generally correspond
209to DNS message or RR fields.
210
211Many of them are expected to be integral values, for which either decimal or
212hexadecimal representation is accepted, for example:
213
214  rr_ttl: 3600
215  tag: 0x1234
216
217Some others are expected to be string.  A string value does not have
218to be quoted:
219
220  address: 192.0.2.2
221
222but can also be quoted with single quotes:
223
224  address: '192.0.2.2'
225
226Note 1: a string that can be interpreted as an integer must be quoted.
227For example, if you want to set a "string" entry to "3600", it should
228be:
229
230  string: '3600'
231
232instead of
233
234  string: 3600
235
236Note 2: a string enclosed with double quotes is not accepted:
237
238  # This doesn't work:
239  address: "192.0.2.2"
240
241In general, string values are converted to hexadecimal sequences
242according to the semantics of the entry.  For instance, a textual IPv4
243address in the above example will be converted to a hexadecimal
244sequence corresponding to a 4-byte integer.  So, in many cases, the
245acceptable syntax for a particular string entry value should be
246obvious from the context.  There are still some exceptional cases
247especially for complicated RR field values, for which the
248corresponding class documentation should be referenced.
249
250One special string syntax that would be worth noting is domain names,
251which would naturally be used in many kinds of entries.  The simplest
252form of acceptable syntax is a textual representation of domain names
253such as "example.com" (note: names are always assumed to be
254"absolute", so the trailing dot can be omitted).  But a domain name in
255the wire format can also contain a compression pointer.  This script
256provides a simple support for name compression with a special notation
257of "ptr=nn" where nn is the numeric pointer value (decimal).  For example,
258if the NSDNAME field of an NS RDATA is specified as follows:
259
260  nsname: ns.ptr=12
261
262this script will generate the following output:
263
264  # NS name=ns.ptr=12
265  026e73c00c
266
267** EXTEND THE SCRIPT **
268
269This script is expected to be extended as we add more support for
270various types of RR.  It is encouraged to add support for a new type
271of RR to this script as we see the need for testing that type.  Here
272is a simple instruction of how to do that.
273
274Assume you are adding support for "FOO" RR.  Also assume that the FOO
275RDATA contains a single field named "value".
276
277What you are expected to do is as follows:
278
279- Define a new class named "FOO" inherited from the RR class.  Also
280  define a class variable named "value" for the FOO RDATA field (the
281  variable name can be different from the field name, but it's
282  convenient if it can be easily identifiable.) with an appropriate
283  default value (if possible):
284
285    class FOO(RR):
286        value = 10
287
288  The name of the variable will be (automatically) used as the
289  corresponding entry name in the spec file.  So, a spec file that
290  sets this field to 20 would look like this:
291
292    [foo]
293    value: 20
294
295- Define the "dump()" method for class FOO.  It must call
296  self.dump_header() (which is derived from class RR) at the
297  beginning.  It then prints the RDATA field values in an appropriate
298  way.  Assuming the value is a 16-bit integer field, a complete
299  dump() method would look like this:
300
301    def dump(self, f):
302        if self.rdlen is None:
303            self.rdlen = 2
304        self.dump_header(f, self.rdlen)
305        f.write('# Value=%d\\n' % (self.value))
306        f.write('%04x\\n' % (self.value))
307
308  The first f.write() call is not mandatory, but is encouraged to
309  be provided so that the generated files will be more human readable.
310  Depending on the complexity of the RDATA fields, the dump()
311  implementation would be more complicated.  In particular, if the
312  RDATA length is variable and the RDLEN field value is not specified
313  in the spec file, the dump() method is normally expected to
314  calculate the correct length and pass it to dump_header().  See the
315  implementation of various derived classes of class RR for actual
316  examples.
317"""
318
319import configparser, re, time, socket, sys, base64
320from datetime import datetime
321from optparse import OptionParser
322
323re_hex = re.compile(r'^0x[0-9a-fA-F]+')
324re_decimal = re.compile(r'^\d+$')
325re_string = re.compile(r"\'(.*)\'$")
326
327dnssec_timefmt = '%Y%m%d%H%M%S'
328
329dict_qr = { 'query' : 0, 'response' : 1 }
330dict_opcode = { 'query' : 0, 'iquery' : 1, 'status' : 2, 'notify' : 4,
331                'update' : 5 }
332rdict_opcode = dict([(dict_opcode[k], k.upper()) for k in dict_opcode.keys()])
333dict_rcode = { 'noerror' : 0, 'formerr' : 1, 'servfail' : 2, 'nxdomain' : 3,
334               'notimp' : 4, 'refused' : 5, 'yxdomain' : 6, 'yxrrset' : 7,
335               'nxrrset' : 8, 'notauth' : 9, 'notzone' : 10 }
336rdict_rcode = dict([(dict_rcode[k], k.upper()) for k in dict_rcode.keys()])
337dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
338                'soa' : 6, 'mb' : 7, 'mg' : 8, 'mr' : 9, 'null' : 10,
339                'wks' : 11, 'ptr' : 12, 'hinfo' : 13, 'minfo' : 14, 'mx' : 15,
340                'txt' : 16, 'rp' : 17, 'afsdb' : 18, 'x25' : 19, 'isdn' : 20,
341                'rt' : 21, 'nsap' : 22, 'nsap_tr' : 23, 'sig' : 24, 'key' : 25,
342                'px' : 26, 'gpos' : 27, 'aaaa' : 28, 'loc' : 29, 'nxt' : 30,
343                'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
344                'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
345                'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
346                'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'tlsa' : 52, 'hip' : 55,
347                'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
348                'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
349                'maila' : 254, 'any' : 255, 'caa' : 257 }
350rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
351dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
352rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
353                          dict_rrclass.keys()])
354dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
355                   'rsasha1' : 5 }
356dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
357rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
358                            dict_algorithm.keys()])
359rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
360                                  dict_nsec3_algorithm.keys()])
361
362header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
363                   'rcode' : dict_rcode }
364question_xtables = { 'rrtype' : dict_rrtype, 'rrclass' : dict_rrclass }
365
366def parse_value(value, xtable = {}):
367    if re.search(re_hex, value):
368        return int(value, 16)
369    if re.search(re_decimal, value):
370        return int(value)
371    m = re.match(re_string, value)
372    if m:
373        return m.group(1)
374    lovalue = value.lower()
375    if lovalue in xtable:
376        return xtable[lovalue]
377    return value
378
379def code_totext(code, dict):
380    if code in dict.keys():
381        return dict[code] + '(' + str(code) + ')'
382    return str(code)
383
384def encode_name(name, absolute=True):
385    # make sure the name is dot-terminated.  duplicate dots will be ignored
386    # below.
387    name += '.'
388    labels = name.split('.')
389    wire = ''
390    for l in labels:
391        if len(l) > 4 and l[0:4] == 'ptr=':
392            # special meta-syntax for compression pointer
393            wire += '%04x' % (0xc000 | int(l[4:]))
394            break
395        if absolute or len(l) > 0:
396            wire += '%02x' % len(l)
397            wire += ''.join(['%02x' % ord(ch) for ch in l])
398        if len(l) == 0:
399            break
400    return wire
401
402def encode_string(name, len=None):
403    if type(name) is int and len is not None:
404        return '%0.*x' % (len * 2, name)
405    return ''.join(['%02x' % ord(ch) for ch in name])
406
407def encode_bytes(name, len=None):
408    if type(name) is int and len is not None:
409        return '%0.*x' % (len * 2, name)
410    return ''.join(['%02x' % ch for ch in name])
411
412def count_namelabels(name):
413    if name == '.':             # special case
414        return 0
415    m = re.match('^(.*)\.$', name)
416    if m:
417        name = m.group(1)
418    return len(name.split('.'))
419
420def get_config(config, section, configobj, xtables = {}):
421    try:
422        for field in config.options(section):
423            value = config.get(section, field)
424            if field in xtables.keys():
425                xtable = xtables[field]
426            else:
427                xtable = {}
428            configobj.__dict__[field] = parse_value(value, xtable)
429    except configparser.NoSectionError:
430        return False
431    return True
432
433def print_header(f, input_file):
434    f.write('''###
435### This data file was auto-generated from ''' + input_file + '''
436###
437''')
438
439class Name:
440    '''Implements rendering a single domain name in the test data format.
441
442    Configurable parameter is as follows (see the description of the
443    same name of attribute for the default value):
444    - name (string): A textual representation of the name, such as
445      'example.com'.
446    - pointer (int): If specified, compression pointer will be
447      prepended to the generated data with the offset being the value
448      of this parameter.
449    '''
450
451    name = 'example.com'
452    pointer = None                # no compression by default
453    def dump(self, f):
454        name = self.name
455        if self.pointer is not None:
456            if len(name) > 0 and name[-1] != '.':
457                name += '.'
458            name += 'ptr=%d' % self.pointer
459        name_wire = encode_name(name)
460        f.write('\n# DNS Name: %s' % self.name)
461        if self.pointer is not None:
462            f.write(' + compression pointer: %d' % self.pointer)
463        f.write('\n')
464        f.write('%s' % name_wire)
465        f.write('\n')
466
467class DNSHeader:
468    '''Implements rendering a DNS Header section in the test data format.
469
470    Configurable parameter is as follows (see the description of the
471    same name of attribute for the default value):
472    - id (16-bit int):
473    - qr, aa, tc, rd, ra, ad, cd (0 or 1): Standard header bits as
474      defined in RFC1035 and RFC4035.  If set to 1, the corresponding
475      bit will be set; if set to 0, it will be cleared.
476    - mbz (0-3): The reserved field of the 3rd and 4th octets of the
477      header.
478    - rcode (4-bit int or string): The RCODE field.  If specified as a
479      string, it must be the commonly used textual mnemonic of the RCODEs
480      (NOERROR, FORMERR, etc, case insensitive).
481    - opcode (4-bit int or string): The OPCODE field.  If specified as
482      a string, it must be the commonly used textual mnemonic of the
483      OPCODEs (QUERY, NOTIFY, etc, case insensitive).
484    - qdcount, ancount, nscount, arcount (16-bit int): The QD/AN/NS/AR
485      COUNT fields, respectively.
486    '''
487
488    id = 0x1035
489    (qr, aa, tc, rd, ra, ad, cd) = 0, 0, 0, 0, 0, 0, 0
490    mbz = 0
491    rcode = 0                   # noerror
492    opcode = 0                  # query
493    (qdcount, ancount, nscount, arcount) = 1, 0, 0, 0
494
495    def dump(self, f):
496        f.write('\n# Header Section\n')
497        f.write('# ID=' + str(self.id))
498        f.write(' QR=' + ('Response' if self.qr else 'Query'))
499        f.write(' Opcode=' + code_totext(self.opcode, rdict_opcode))
500        f.write(' Rcode=' + code_totext(self.rcode, rdict_rcode))
501        f.write('%s' % (' AA' if self.aa else ''))
502        f.write('%s' % (' TC' if self.tc else ''))
503        f.write('%s' % (' RD' if self.rd else ''))
504        f.write('%s' % (' AD' if self.ad else ''))
505        f.write('%s' % (' CD' if self.cd else ''))
506        f.write('\n')
507        f.write('%04x ' % self.id)
508        flag_and_code = 0
509        flag_and_code |= (self.qr << 15 | self.opcode << 14 | self.aa << 10 |
510                          self.tc << 9 | self.rd << 8 | self.ra << 7 |
511                          self.mbz << 6 | self.ad << 5 | self.cd << 4 |
512                          self.rcode)
513        f.write('%04x\n' % flag_and_code)
514        f.write('# QDCNT=%d, ANCNT=%d, NSCNT=%d, ARCNT=%d\n' %
515                (self.qdcount, self.ancount, self.nscount, self.arcount))
516        f.write('%04x %04x %04x %04x\n' % (self.qdcount, self.ancount,
517                                           self.nscount, self.arcount))
518
519class DNSQuestion:
520    '''Implements rendering a DNS question in the test data format.
521
522    Configurable parameter is as follows (see the description of the
523    same name of attribute for the default value):
524    - name (string): The QNAME.  The string must be interpreted as a
525      valid domain name.
526    - rrtype (int or string): The question type.  If specified
527      as an integer, it must be the 16-bit RR type value of the
528      covered type.  If specified as a string, it must be the textual
529      mnemonic of the type.
530    - rrclass (int or string): The question class.  If specified as an
531      integer, it must be the 16-bit RR class value of the covered
532      type.  If specified as a string, it must be the textual mnemonic
533      of the class.
534    '''
535    name = 'example.com.'
536    rrtype = parse_value('A', dict_rrtype)
537    rrclass = parse_value('IN', dict_rrclass)
538
539    def dump(self, f):
540        f.write('\n# Question Section\n')
541        f.write('# QNAME=%s QTYPE=%s QCLASS=%s\n' %
542                (self.name,
543                 code_totext(self.rrtype, rdict_rrtype),
544                 code_totext(self.rrclass, rdict_rrclass)))
545        f.write(encode_name(self.name))
546        f.write(' %04x %04x\n' % (self.rrtype, self.rrclass))
547
548class EDNS:
549    '''Implements rendering EDNS OPT RR in the test data format.
550
551    Configurable parameter is as follows (see the description of the
552    same name of attribute for the default value):
553    - name (string): The owner name of the OPT RR.  The string must be
554      interpreted as a valid domain name.
555    - udpsize (16-bit int): The UDP payload size (set as the RR class)
556    - extrcode (8-bit int): The upper 8 bits of the extended RCODE.
557    - version (8-bit int): The EDNS version.
558    - do (int): The DNSSEC DO bit.  The bit will be set if this value
559      is 1; otherwise the bit will be unset.
560    - mbz (15-bit int): The rest of the flags field.
561    - rdlen (16-bit int): The RDLEN field.  Note: right now specifying
562      a non 0 value (except for making bogus data) doesn't make sense
563      because there is no way to configure RDATA.
564    '''
565    name = '.'
566    udpsize = 4096
567    extrcode = 0
568    version = 0
569    do = 0
570    mbz = 0
571    rdlen = 0
572    def dump(self, f):
573        f.write('\n# EDNS OPT RR\n')
574        f.write('# NAME=%s TYPE=%s UDPSize=%d ExtRcode=%s Version=%s DO=%d\n' %
575                (self.name, code_totext(dict_rrtype['opt'], rdict_rrtype),
576                 self.udpsize, self.extrcode, self.version,
577                 1 if self.do else 0))
578
579        code_vers = (self.extrcode << 8) | (self.version & 0x00ff)
580        extflags = (self.do << 15) | (self.mbz & ~0x8000)
581        f.write('%s %04x %04x %04x %04x\n' %
582                (encode_name(self.name), dict_rrtype['opt'], self.udpsize,
583                 code_vers, extflags))
584        f.write('# RDLEN=%d\n' % self.rdlen)
585        f.write('%04x\n' % self.rdlen)
586
587class RR:
588    '''This is a base class for various types of RR test data.
589    For each RR type (A, AAAA, NS, etc), we define a derived class of RR
590    to dump type specific RDATA parameters.  This class defines parameters
591    common to all types of RDATA, namely the owner name, RR class and TTL.
592    The dump() method of derived classes are expected to call dump_header(),
593    whose default implementation is provided in this class.  This method
594    decides whether to dump the test data as an RR (with name, type, class)
595    or only as RDATA (with its length), and dumps the corresponding data
596    via the specified file object.
597
598    By convention we assume derived classes are named after the common
599    standard mnemonic of the corresponding RR types.  For example, the
600    derived class for the RR type SOA should be named "SOA".
601
602    Configurable parameters are as follows:
603    - as_rr (bool): Whether or not the data is to be dumped as an RR.
604      False by default.
605    - rr_name (string): The owner name of the RR.  The string must be
606      interpreted as a valid domain name (compression pointer can be
607      contained).  Default is 'example.com.'
608    - rr_class (string): The RR class of the data.  Only meaningful
609      when the data is dumped as an RR.  Default is 'IN'.
610    - rr_ttl (int): The TTL value of the RR.  Only meaningful when
611      the data is dumped as an RR.  Default is 86400 (1 day).
612    - rdlen (int): 16-bit RDATA length.  It can be None (i.e. omitted
613      in the spec file), in which case the actual length of the
614      generated RDATA is automatically determined and used; if
615      negative, the RDLEN field will be omitted from the output data.
616      (Note that omitting RDLEN with as_rr being True is mostly
617      meaningless, although the script doesn't complain about it).
618      Default is None.
619    '''
620
621    def __init__(self):
622        self.as_rr = False
623        # only when as_rr is True, same for class/TTL:
624        self.rr_name = 'example.com'
625        self.rr_class = 'IN'
626        self.rr_ttl = 86400
627        self.rdlen = None
628
629    def dump_header(self, f, rdlen):
630        type_txt = self.__class__.__name__
631        type_code = parse_value(type_txt, dict_rrtype)
632        rdlen_spec = ''
633        rdlen_data = ''
634        if rdlen >= 0:
635            rdlen_spec = ', RDLEN=%d' % rdlen
636            rdlen_data = '%04x' % rdlen
637        if self.as_rr:
638            rrclass = parse_value(self.rr_class, dict_rrclass)
639            f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d%s)\n' %
640                    (type_txt, self.rr_name,
641                     code_totext(rrclass, rdict_rrclass), self.rr_ttl,
642                     rdlen_spec))
643            f.write('%s %04x %04x %08x %s\n' %
644                    (encode_name(self.rr_name), type_code, rrclass,
645                     self.rr_ttl, rdlen_data))
646        else:
647            f.write('\n# %s RDATA%s\n' % (type_txt, rdlen_spec))
648            f.write('%s\n' % rdlen_data)
649
650class A(RR):
651    '''Implements rendering A RDATA (of class IN) in the test data format.
652
653    Configurable parameter is as follows (see the description of the
654    same name of attribute for the default value):
655    - address (string): The address field.  This must be a valid textual
656      IPv4 address.
657    '''
658    RDLEN_DEFAULT = 4           # fixed by default
659    address = '192.0.2.1'
660
661    def dump(self, f):
662        if self.rdlen is None:
663            self.rdlen = self.RDLEN_DEFAULT
664        self.dump_header(f, self.rdlen)
665        f.write('# Address=%s\n' % (self.address))
666        bin_address = socket.inet_aton(self.address)
667        f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
668                                        bin_address[2], bin_address[3]))
669
670class AAAA(RR):
671    '''Implements rendering AAAA RDATA (of class IN) in the test data
672    format.
673
674    Configurable parameter is as follows (see the description of the
675    same name of attribute for the default value):
676    - address (string): The address field.  This must be a valid textual
677      IPv6 address.
678    '''
679    RDLEN_DEFAULT = 16          # fixed by default
680    address = '2001:db8::1'
681
682    def dump(self, f):
683        if self.rdlen is None:
684            self.rdlen = self.RDLEN_DEFAULT
685        self.dump_header(f, self.rdlen)
686        f.write('# Address=%s\n' % (self.address))
687        bin_address = socket.inet_pton(socket.AF_INET6, self.address)
688        [f.write('%02x' % x) for x in bin_address]
689        f.write('\n')
690
691class NS(RR):
692    '''Implements rendering NS RDATA in the test data format.
693
694    Configurable parameter is as follows (see the description of the
695    same name of attribute for the default value):
696    - nsname (string): The NSDNAME field.  The string must be
697      interpreted as a valid domain name.
698    '''
699
700    nsname = 'ns.example.com'
701
702    def dump(self, f):
703        nsname_wire = encode_name(self.nsname)
704        if self.rdlen is None:
705            self.rdlen = len(nsname_wire) / 2
706        self.dump_header(f, self.rdlen)
707        f.write('# NS name=%s\n' % (self.nsname))
708        f.write('%s\n' % nsname_wire)
709
710class SOA(RR):
711    '''Implements rendering SOA RDATA in the test data format.
712
713    Configurable parameters are as follows (see the description of the
714    same name of attribute for the default value):
715    - mname/rname (string): The MNAME/RNAME fields, respectively.  The
716      string must be interpreted as a valid domain name.
717    - serial (32-bit int): The SERIAL field
718    - refresh (32-bit int): The REFRESH field
719    - retry (32-bit int): The RETRY field
720    - expire (32-bit int): The EXPIRE field
721    - minimum (32-bit int): The MINIMUM field
722    '''
723
724    mname = 'ns.example.com'
725    rname = 'root.example.com'
726    serial = 2010012601
727    refresh = 3600
728    retry = 300
729    expire = 3600000
730    minimum = 1200
731    def dump(self, f):
732        mname_wire = encode_name(self.mname)
733        rname_wire = encode_name(self.rname)
734        if self.rdlen is None:
735            self.rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
736        self.dump_header(f, self.rdlen)
737        f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
738        f.write('%s %s\n' % (mname_wire, rname_wire))
739        f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
740                (self.serial, self.refresh, self.retry, self.expire,
741                 self.minimum))
742        f.write('%08x %08x %08x %08x %08x\n' % (self.serial, self.refresh,
743                                                self.retry, self.expire,
744                                                self.minimum))
745
746class TXT(RR):
747    '''Implements rendering TXT RDATA in the test data format.
748
749    Configurable parameters are as follows (see the description of the
750    same name of attribute for the default value):
751    - nstring (int): number of character-strings
752    - stringlenN (int) (int, N = 0, ..., nstring-1): the length of the
753      N-th character-string.
754    - stringN (string, N = 0, ..., nstring-1): the N-th
755      character-string.
756    - stringlen (int): the default string.  If nstring >= 1 and the
757      corresponding stringlenN isn't specified in the spec file, this
758      value will be used.  If this parameter isn't specified either,
759      the length of the string will be used.  Note that it means
760      this parameter (or any stringlenN) doesn't have to be specified
761      unless you want to intentionally build a broken character string.
762    - string (string): the default string.  If nstring >= 1 and the
763      corresponding stringN isn't specified in the spec file, this
764      string will be used.
765    '''
766
767    nstring = 1
768    stringlen = None
769    string = 'Test-String'
770
771    def dump(self, f):
772        stringlen_list = []
773        string_list = []
774        wirestring_list = []
775        for i in range(0, self.nstring):
776            key_string = 'string' + str(i)
777            if key_string in self.__dict__:
778                string_list.append(self.__dict__[key_string])
779            else:
780                string_list.append(self.string)
781            wirestring_list.append(encode_string(string_list[-1]))
782            key_stringlen = 'stringlen' + str(i)
783            if key_stringlen in self.__dict__:
784                stringlen_list.append(self.__dict__[key_stringlen])
785            else:
786                stringlen_list.append(self.stringlen)
787            if stringlen_list[-1] is None:
788                stringlen_list[-1] = int(len(wirestring_list[-1]) / 2)
789        if self.rdlen is None:
790            self.rdlen = int(len(''.join(wirestring_list)) / 2) + self.nstring
791        self.dump_header(f, self.rdlen)
792        for i in range(0, self.nstring):
793            f.write('# String Len=%d, String=\"%s\"\n' %
794                    (stringlen_list[i], string_list[i]))
795            f.write('%02x%s%s\n' % (stringlen_list[i],
796                                    ' ' if len(wirestring_list[i]) > 0 else '',
797                                    wirestring_list[i]))
798
799class RP(RR):
800    '''Implements rendering RP RDATA in the test data format.
801
802    Configurable parameters are as follows (see the description of the
803    same name of attribute for the default value):
804    - mailbox (string): The mailbox field.
805    - text (string): The text field.
806    These strings must be interpreted as a valid domain name.
807    '''
808    mailbox = 'root.example.com'
809    text = 'rp-text.example.com'
810    def dump(self, f):
811        mailbox_wire = encode_name(self.mailbox)
812        text_wire = encode_name(self.text)
813        if self.rdlen is None:
814            self.rdlen = (len(mailbox_wire) + len(text_wire)) / 2
815        else:
816            self.rdlen = int(self.rdlen)
817        self.dump_header(f, self.rdlen)
818        f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
819        f.write('%s %s\n' % (mailbox_wire, text_wire))
820
821class SSHFP(RR):
822    '''Implements rendering SSHFP RDATA in the test data format.
823
824    Configurable parameters are as follows (see the description of the
825    same name of attribute for the default value):
826    - algorithm (int): The algorithm number.
827    - fingerprint_type (int): The fingerprint type.
828    - fingerprint (string): The fingerprint.
829    '''
830    algorithm = 2
831    fingerprint_type = 1
832    fingerprint = '123456789abcdef67890123456789abcdef67890'
833    def dump(self, f):
834        if self.rdlen is None:
835            self.rdlen = 2 + (len(self.fingerprint) / 2)
836        else:
837            self.rdlen = int(self.rdlen)
838        self.dump_header(f, self.rdlen)
839        f.write('# ALGORITHM=%d FINGERPRINT_TYPE=%d FINGERPRINT=%s\n' % (self.algorithm,
840                                                                         self.fingerprint_type,
841                                                                         self.fingerprint))
842        f.write('%02x %02x %s\n' % (self.algorithm, self.fingerprint_type, self.fingerprint))
843
844class MINFO(RR):
845    '''Implements rendering MINFO RDATA in the test data format.
846
847    Configurable parameters are as follows (see the description of the
848    same name of attribute for the default value):
849    - rmailbox (string): The rmailbox field.
850    - emailbox (string): The emailbox field.
851    These strings must be interpreted as a valid domain name.
852    '''
853    rmailbox = 'rmailbox.example.com'
854    emailbox = 'emailbox.example.com'
855    def dump(self, f):
856        rmailbox_wire = encode_name(self.rmailbox)
857        emailbox_wire = encode_name(self.emailbox)
858        if self.rdlen is None:
859            self.rdlen = (len(rmailbox_wire) + len(emailbox_wire)) / 2
860        else:
861            self.rdlen = int(self.rdlen)
862        self.dump_header(f, self.rdlen)
863        f.write('# RMAILBOX=%s EMAILBOX=%s\n' % (self.rmailbox, self.emailbox))
864        f.write('%s %s\n' % (rmailbox_wire, emailbox_wire))
865
866class AFSDB(RR):
867    '''Implements rendering AFSDB RDATA in the test data format.
868
869    Configurable parameters are as follows (see the description of the
870    same name of attribute for the default value):
871    - subtype (16 bit int): The subtype field.
872    - server (string): The server field.
873    The string must be interpreted as a valid domain name.
874    '''
875    subtype = 1
876    server = 'afsdb.example.com'
877    def dump(self, f):
878        server_wire = encode_name(self.server)
879        if self.rdlen is None:
880            self.rdlen = 2 + len(server_wire) / 2
881        else:
882            self.rdlen = int(self.rdlen)
883        self.dump_header(f, self.rdlen)
884        f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
885        f.write('%04x %s\n' % (self.subtype, server_wire))
886
887class CAA(RR):
888    '''Implements rendering CAA RDATA in the test data format.
889
890    Configurable parameters are as follows (see the description of the
891    same name of attribute for the default value):
892    - flags (int): The flags field.
893    - tag (string): The tag field.
894    - value (string): The value field.
895    '''
896    flags = 0
897    tag = 'issue'
898    value = 'ca.example.net'
899    def dump(self, f):
900        if self.rdlen is None:
901            self.rdlen = 1 + 1 + len(self.tag) + len(self.value)
902        else:
903            self.rdlen = int(self.rdlen)
904        self.dump_header(f, self.rdlen)
905        f.write('# FLAGS=%d TAG=%s VALUE=%s\n' % \
906                (self.flags, self.tag, self.value))
907        f.write('%02x %02x ' % \
908                (self.flags, len(self.tag)))
909        f.write(encode_string(self.tag))
910        f.write(encode_string(self.value))
911        f.write('\n')
912
913class DNSKEY(RR):
914    '''Implements rendering DNSKEY RDATA in the test data format.
915
916    Configurable parameters are as follows (see code below for the
917    default values):
918    - flags (16-bit int): The flags field.
919    - protocol (8-bit int): The protocol field.
920    - algorithm (8-bit int): The algorithm field.
921    - digest (string): The key digest field.
922    '''
923    flags = 257
924    protocol = 3
925    algorithm = 5
926    digest = 'AAECAwQFBgcICQoLDA0ODw=='
927
928    def dump(self, f):
929        decoded_digest = base64.b64decode(bytes(self.digest, 'ascii'))
930        if self.rdlen is None:
931            self.rdlen = 4 + len(decoded_digest)
932        else:
933            self.rdlen = int(self.rdlen)
934
935        self.dump_header(f, self.rdlen)
936
937        f.write('# FLAGS=%d\n' % (self.flags))
938        f.write('%04x\n' % (self.flags))
939
940        f.write('# PROTOCOL=%d\n' % (self.protocol))
941        f.write('%02x\n' % (self.protocol))
942
943        f.write('# ALGORITHM=%d\n' % (self.algorithm))
944        f.write('%02x\n' % (self.algorithm))
945
946        f.write('# DIGEST=%s\n' % (self.digest))
947        f.write('%s\n' % (encode_bytes(decoded_digest)))
948
949class NSECBASE(RR):
950    '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
951    these RRs.  The NSEC and NSEC3 classes will be inherited from this
952    class.
953
954    Configurable parameters are as follows (see the description of the
955    same name of attribute for the default value):
956    - nbitmap (int): The number of type bitmaps.
957    The following three define the bitmaps.  If suffixed with "N"
958    (0 <= N < nbitmaps), it means the definition for the N-th bitmap.
959    If there is no suffix (e.g., just "block", it means the default
960    for any unspecified values)
961    - block[N] (8-bit int): The Window Block.
962    - maplen[N] (8-bit int): The Bitmap Length.  The default "maplen"
963      can also be unspecified (with being set to None), in which case
964      the corresponding length will be calculated from the bitmap.
965    - bitmap[N] (string): The Bitmap.  This must be the hexadecimal
966      representation of the bitmap field.  For example, for a bitmap
967      where the 7th and 15th bits (and only these bits) are set, it
968      must be '0101'.  Note also that the value must be quoted with
969      single quotations because it could also be interpreted as an
970      integer.
971    '''
972    nbitmap = 1                 # number of bitmaps
973    block = 0
974    maplen = None              # default bitmap length, auto-calculate
975    bitmap = '040000000003'     # an arbitrarily chosen bitmap sample
976    def dump(self, f):
977        # first, construct the bitmap data
978        block_list = []
979        maplen_list = []
980        bitmap_list = []
981        for i in range(0, self.nbitmap):
982            key_bitmap = 'bitmap' + str(i)
983            if key_bitmap in self.__dict__:
984                bitmap_list.append(self.__dict__[key_bitmap])
985            else:
986                bitmap_list.append(self.bitmap)
987            key_maplen = 'maplen' + str(i)
988            if key_maplen in self.__dict__:
989                maplen_list.append(self.__dict__[key_maplen])
990            else:
991                maplen_list.append(self.maplen)
992            if maplen_list[-1] is None: # calculate it if not specified
993                maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
994            key_block = 'block' + str(i)
995            if key_block in self.__dict__:
996               block_list.append(self.__dict__[key_block])
997            else:
998                block_list.append(self.block)
999
1000        # dump RR-type specific part (NSEC or NSEC3)
1001        self.dump_fixedpart(f, 2 * self.nbitmap + \
1002                                int(len(''.join(bitmap_list)) / 2))
1003
1004        # dump the bitmap
1005        for i in range(0, self.nbitmap):
1006            f.write('# Bitmap: Block=%d, Length=%d\n' %
1007                    (block_list[i], maplen_list[i]))
1008            f.write('%02x %02x %s\n' %
1009                    (block_list[i], maplen_list[i], bitmap_list[i]))
1010
1011class NSEC(NSECBASE):
1012    '''Implements rendering NSEC RDATA in the test data format.
1013
1014    Configurable parameters are as follows (see the description of the
1015    same name of attribute for the default value):
1016    - Type bitmap related parameters: see class NSECBASE
1017    - nextname (string): The Next Domain Name field.  The string must be
1018      interpreted as a valid domain name.
1019    '''
1020
1021    nextname = 'next.example.com'
1022    def dump_fixedpart(self, f, bitmap_totallen):
1023        name_wire = encode_name(self.nextname)
1024        if self.rdlen is None:
1025            # if rdlen needs to be calculated, it must be based on the bitmap
1026            # length, because the configured maplen can be fake.
1027            self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
1028        self.dump_header(f, self.rdlen)
1029        f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
1030                                                 int(len(name_wire) / 2)))
1031        f.write('%s\n' % name_wire)
1032
1033class NSEC3PARAM(RR):
1034    '''Implements rendering NSEC3PARAM RDATA in the test data format.
1035
1036    Configurable parameters are as follows (see the description of the
1037    same name of attribute for the default value):
1038    - hashalg (8-bit int): The Hash Algorithm field.  Note that
1039      currently the only defined algorithm is SHA-1, for which a value
1040      of 1 will be used, and it's the default.  So this implementation
1041      does not support any string representation right now.
1042    - optout (bool): The Opt-Out flag of the Flags field.
1043    - mbz (7-bit int): The rest of the Flags field.  This value will
1044      be left shifted for 1 bit and then OR-ed with optout to
1045      construct the complete Flags field.
1046    - iterations (16-bit int): The Iterations field.
1047    - saltlen (int): The Salt Length field.
1048    - salt (string): The Salt field.  It is converted to a sequence of
1049      ascii codes and its hexadecimal representation will be used.
1050    '''
1051
1052    hashalg = 1                 # SHA-1
1053    optout = False              # opt-out flag
1054    mbz = 0                     # other flag fields (none defined yet)
1055    iterations = 1
1056    saltlen = 5
1057    salt = 's' * saltlen
1058
1059    def dump(self, f):
1060        if self.rdlen is None:
1061            self.rdlen = 4 + 1 + len(self.salt)
1062        self.dump_header(f, self.rdlen)
1063        self._dump_params(f)
1064
1065    def _dump_params(self, f):
1066        '''This method is intended to be shared with NSEC3 class.
1067
1068        '''
1069
1070        optout_val = 1 if self.optout else 0
1071        f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
1072                (code_totext(self.hashalg, rdict_nsec3_algorithm),
1073                 optout_val, self.mbz, self.iterations))
1074        f.write('%02x %02x %04x\n' %
1075                (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
1076        f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
1077        f.write('%02x%s%s\n' % (self.saltlen,
1078                                ' ' if len(self.salt) > 0 else '',
1079                                encode_string(self.salt)))
1080
1081class NSEC3(NSECBASE, NSEC3PARAM):
1082    '''Implements rendering NSEC3 RDATA in the test data format.
1083
1084    Configurable parameters are as follows (see the description of the
1085    same name of attribute for the default value):
1086    - Type bitmap related parameters: see class NSECBASE
1087    - Hash parameter related parameters: see class NSEC3PARAM
1088    - hashlen (int): The Hash Length field.
1089    - hash (string): The Next Hashed Owner Name field.  This parameter
1090      is interpreted as "salt".
1091    '''
1092
1093    hashlen = 20
1094    hash = 'h' * hashlen
1095    def dump_fixedpart(self, f, bitmap_totallen):
1096        if self.rdlen is None:
1097            # if rdlen needs to be calculated, it must be based on the bitmap
1098            # length, because the configured maplen can be fake.
1099            self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
1100                + bitmap_totallen
1101        self.dump_header(f, self.rdlen)
1102        self._dump_params(f)
1103        f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
1104        f.write('%02x%s%s\n' % (self.hashlen,
1105                                ' ' if len(self.hash) > 0 else '',
1106                                encode_string(self.hash)))
1107
1108class RRSIG(RR):
1109    '''Implements rendering RRSIG RDATA in the test data format.
1110
1111    Configurable parameters are as follows (see the description of the
1112    same name of attribute for the default value):
1113    - covered (int or string): The Type Covered field.  If specified
1114      as an integer, it must be the 16-bit RR type value of the
1115      covered type.  If specified as a string, it must be the textual
1116      mnemonic of the type.
1117    - algorithm (int or string): The Algorithm field.   If specified
1118      as an integer, it must be the 8-bit algorithm number as defined
1119      in RFC4034.  If specified as a string, it must be one of the keys
1120      of dict_algorithm (case insensitive).
1121    - labels (int): The Labels field.  If omitted (the corresponding
1122      variable being set to None), the number of labels of "signer"
1123      (excluding the trailing null label as specified in RFC4034) will
1124      be used.
1125    - originalttl (32-bit int): The Original TTL field.
1126    - expiration (32-bit int): The Expiration TTL field.
1127    - inception (32-bit int): The Inception TTL field.
1128    - tag (16-bit int): The Key Tag field.
1129    - signer (string): The Signer's Name field.  The string must be
1130      interpreted as a valid domain name.
1131    - signature (int): The Signature field.  Right now only a simple
1132      integer form is supported.  A prefix of "0" will be prepended if
1133      the resulting hexadecimal representation consists of an odd
1134      number of characters.
1135    '''
1136
1137    covered = 'A'
1138    algorithm = 'RSASHA1'
1139    labels = None                 # auto-calculate (#labels of signer)
1140    originalttl = 3600
1141    expiration = int(time.mktime(datetime.strptime('20100131120000',
1142                                                   dnssec_timefmt).timetuple()))
1143    inception = int(time.mktime(datetime.strptime('20100101120000',
1144                                                  dnssec_timefmt).timetuple()))
1145    tag = 0x1035
1146    signer = 'example.com'
1147    signature = 0x123456789abcdef123456789abcdef
1148
1149    def dump(self, f):
1150        name_wire = encode_name(self.signer)
1151        sig_wire = '%x' % self.signature
1152        if len(sig_wire) % 2 != 0:
1153            sig_wire = '0' + sig_wire
1154        if self.rdlen is None:
1155            self.rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2)
1156        self.dump_header(f, self.rdlen)
1157
1158        if type(self.covered) is str:
1159            self.covered = dict_rrtype[self.covered.lower()]
1160        if type(self.algorithm) is str:
1161            self.algorithm = dict_algorithm[self.algorithm.lower()]
1162        if self.labels is None:
1163            self.labels = count_namelabels(self.signer)
1164        f.write('# Covered=%s Algorithm=%s Labels=%d OrigTTL=%d\n' %
1165                (code_totext(self.covered, rdict_rrtype),
1166                 code_totext(self.algorithm, rdict_algorithm), self.labels,
1167                 self.originalttl))
1168        f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
1169                                           self.labels, self.originalttl))
1170        f.write('# Expiration=%s, Inception=%s\n' %
1171                (str(self.expiration), str(self.inception)))
1172        f.write('%08x %08x\n' % (self.expiration, self.inception))
1173        f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
1174        f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
1175
1176class TKEY(RR):
1177    '''Implements rendering TKEY RDATA in the test data format.
1178
1179    As a meta RR type TKEY uses some non common parameters.  This
1180    class overrides some of the default attributes of the RR class
1181    accordingly:
1182    - rr_class is set to 'ANY'
1183    - rr_ttl is set to 0
1184    Like other derived classes these can be overridden via the spec
1185    file.
1186
1187    Other configurable parameters are as follows (see the description
1188    of the same name of attribute for the default value):
1189    - algorithm (string): The Algorithm Name field.  The value is
1190      generally interpreted as a domain name string, and will
1191      typically be gss-tsig.
1192    - inception (32-bit int): The Inception TTL field.
1193    - expire (32-bit int): The Expire TTL field.
1194    - mode (16-bit int): The Mode field.
1195    - error (16-bit int): The Error field.
1196    - key_len (int): The Key Len field.
1197    - key (int or string): The Key field.  If specified as an integer,
1198      the integer value is used as the Key, possibly with prepended
1199      0's so that the total length will be key len.  If specified as a
1200      string, it is converted to a sequence of ascii codes and its
1201      hexadecimal representation will be used.  So, for example, if
1202      "key" is set to 'abc', it will be converted to '616263'.  Note
1203      that in this case the length of "key" may not be equal to
1204      key_len.  If unspecified, the key_len number of '78' (ascii
1205      code of 'x') will be used.
1206    - other_len (int): The Other Len field.
1207    - other_data (int or string): The Other Data field.  This is
1208      interpreted just like "key" except that other_len is used
1209      instead of key_len.  If unspecified this will be empty.
1210    '''
1211
1212    algorithm = 'gss-tsig'
1213    inception = int(time.mktime(datetime.strptime('20210501120000',
1214                                                  dnssec_timefmt).timetuple()))
1215    expire = int(time.mktime(datetime.strptime('20210501130000',
1216                                               dnssec_timefmt).timetuple()))
1217    mode = 3                 # GSS-API
1218    error = 0
1219    key_len = 32
1220    key = None               # use 'x' * key_len
1221    other_len = 0
1222    other_data = None
1223
1224    # TKEY has some special defaults
1225    def __init__(self):
1226        super().__init__()
1227        self.rr_class = 'ANY'
1228        self.rr_ttl = 0
1229
1230    def dump(self, f):
1231        name_wire = encode_name(self.algorithm)
1232        key_len = self.key_len
1233        key = self.key
1234        if key is None:
1235            key = encode_string('x' * key_len)
1236        else:
1237            key = encode_string(self.key, key_len)
1238        other_len = self.other_len
1239        if other_len is None:
1240            other_len = 0
1241        other_data = self.other_data
1242        if other_data is None:
1243            other_data = ''
1244        else:
1245            other_data = encode_string(self.other_data, other_len)
1246        if self.rdlen is None:
1247            self.rdlen = int(len(name_wire) / 2 + 16 + len(key) / 2 + \
1248                                 len(other_data) / 2)
1249        self.dump_header(f, self.rdlen)
1250        f.write('# Algorithm=%s\n' % self.algorithm)
1251        f.write('%s\n' % name_wire)
1252        f.write('# Inception=%d Expire=%d Mode=%d Error=%d\n' %
1253                (self.inception, self.expire, self.mode, self.error))
1254        f.write('%08x %08x %04x %04x\n' %
1255                (self.inception, self.expire, self.mode, self.error))
1256        f.write('# Key Len=%d Key=(see hex)\n' % key_len)
1257        f.write('%04x%s\n' % (key_len, ' ' + key if len(key) > 0 else ''))
1258        f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
1259        f.write('%04x%s\n' % (other_len,
1260                              ' ' + other_data if len(other_data) > 0 else ''))
1261
1262class TLSA(RR):
1263    '''Implements rendering TLSA RDATA in the test data format.
1264
1265    Configurable parameters are as follows (see the description of the
1266    same name of attribute for the default value):
1267    - certificate_usage (int): The certificate usage field value.
1268    - selector (int): The selector field value.
1269    - matching_type (int): The matching type field value.
1270    - certificate_association_data (string): The certificate association data.
1271    '''
1272    certificate_usage = 0
1273    selector = 0
1274    matching_type = 1
1275    certificate_association_data = 'd2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971'
1276    def dump(self, f):
1277        if self.rdlen is None:
1278            self.rdlen = 2 + (len(self.certificate_association_data) / 2)
1279        else:
1280            self.rdlen = int(self.rdlen)
1281        self.dump_header(f, self.rdlen)
1282        f.write('# CERTIFICATE_USAGE=%d SELECTOR=%d MATCHING_TYPE=%d CERTIFICATE_ASSOCIATION_DATA=%s\n' %\
1283                (self.certificate_usage, self.selector, self.matching_type,\
1284                 self.certificate_association_data))
1285        f.write('%02x %02x %02x %s\n' % (self.certificate_usage, self.selector, self.matching_type,\
1286                                         self.certificate_association_data))
1287
1288class TSIG(RR):
1289    '''Implements rendering TSIG RDATA in the test data format.
1290
1291    As a meta RR type TSIG uses some non common parameters.  This
1292    class overrides some of the default attributes of the RR class
1293    accordingly:
1294    - rr_class is set to 'ANY'
1295    - rr_ttl is set to 0
1296    Like other derived classes these can be overridden via the spec
1297    file.
1298
1299    Other configurable parameters are as follows (see the description
1300    of the same name of attribute for the default value):
1301    - algorithm (string): The Algorithm Name field.  The value is
1302      generally interpreted as a domain name string, and will
1303      typically be one of the standard algorithm names defined in
1304      RFC4635.  For convenience, however, a shortcut value "hmac-md5"
1305      is allowed instead of the standard "hmac-md5.sig-alg.reg.int".
1306    - time_signed (48-bit int): The Time Signed field.
1307    - fudge (16-bit int): The Fudge field.
1308    - mac_size (int): The MAC Size field.  If omitted, the common value
1309      determined by the algorithm will be used.
1310    - mac (int or string): The MAC field.  If specified as an integer,
1311      the integer value is used as the MAC, possibly with prepended
1312      0's so that the total length will be mac_size.  If specified as a
1313      string, it is converted to a sequence of ascii codes and its
1314      hexadecimal representation will be used.  So, for example, if
1315      "mac" is set to 'abc', it will be converted to '616263'.  Note
1316      that in this case the length of "mac" may not be equal to
1317      mac_size.  If unspecified, the mac_size number of '78' (ascii
1318      code of 'x') will be used.
1319    - original_id (16-bit int): The Original ID field.
1320    - error (16-bit int): The Error field.
1321    - other_len (int): The Other Len field.
1322    - other_data (int or string): The Other Data field.  This is
1323      interpreted just like "mac" except that other_len is used
1324      instead of mac_size.  If unspecified this will be empty unless
1325      the "error" is set to 18 (which means the "BADTIME" error), in
1326      which case a hexadecimal representation of "time_signed + fudge
1327      + 1" will be used.
1328    '''
1329
1330    algorithm = 'hmac-sha256'
1331    time_signed = 1286978795    # arbitrarily chosen default
1332    fudge = 300
1333    mac_size = None             # use a common value for the algorithm
1334    mac = None                  # use 'x' * mac_size
1335    original_id = 2845          # arbitrarily chosen default
1336    error = 0
1337    other_len = None         # 6 if error is BADTIME; otherwise 0
1338    other_data = None        # use time_signed + fudge + 1 for BADTIME
1339    dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
1340
1341    # TSIG has some special defaults
1342    def __init__(self):
1343        super().__init__()
1344        self.rr_class = 'ANY'
1345        self.rr_ttl = 0
1346
1347    def dump(self, f):
1348        if str(self.algorithm) == 'hmac-md5':
1349            name_wire = encode_name('hmac-md5.sig-alg.reg.int')
1350        else:
1351            name_wire = encode_name(self.algorithm)
1352        mac_size = self.mac_size
1353        if mac_size is None:
1354            if self.algorithm in self.dict_macsize.keys():
1355                mac_size = self.dict_macsize[self.algorithm]
1356            else:
1357                raise RuntimeError('TSIG Mac Size cannot be determined')
1358        mac = encode_string('x' * mac_size) if self.mac is None else \
1359            encode_string(self.mac, mac_size)
1360        other_len = self.other_len
1361        if other_len is None:
1362            # 18 = BADTIME
1363            other_len = 6 if self.error == 18 else 0
1364        other_data = self.other_data
1365        if other_data is None:
1366            other_data = '%012x' % (self.time_signed + self.fudge + 1) \
1367                if self.error == 18 else ''
1368        else:
1369            other_data = encode_string(self.other_data, other_len)
1370        if self.rdlen is None:
1371            self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
1372                                 len(other_data) / 2)
1373        self.dump_header(f, self.rdlen)
1374        f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
1375                (self.algorithm, self.time_signed, self.fudge))
1376        f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
1377        f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
1378        f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
1379        f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
1380        f.write('%04x %04x\n' %  (self.original_id, self.error))
1381        f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
1382        f.write('%04x%s\n' % (other_len,
1383                              ' ' + other_data if len(other_data) > 0 else ''))
1384
1385# Build section-class mapping
1386config_param = { 'name' : (Name, {}),
1387                 'header' : (DNSHeader, header_xtables),
1388                 'question' : (DNSQuestion, question_xtables),
1389                 'edns' : (EDNS, {}) }
1390for rrtype in dict_rrtype.keys():
1391    # For any supported RR types add the tuple of (RR_CLASS, {}).
1392    # We expect KeyError as not all the types are supported, and simply
1393    # ignore them.
1394    try:
1395        cur_mod = sys.modules[__name__]
1396        config_param[rrtype] = (cur_mod.__dict__[rrtype.upper()], {})
1397    except KeyError:
1398        pass
1399
1400def get_config_param(section):
1401    s = section
1402    m = re.match('^([^:]+)/\d+$', section)
1403    if m:
1404        s = m.group(1)
1405    return config_param[s]
1406
1407usage = '''usage: %prog [options] input_file'''
1408
1409if __name__ == "__main__":
1410    parser = OptionParser(usage=usage)
1411    parser.add_option('-o', '--output', action='store', dest='output',
1412                      default=None, metavar='FILE',
1413                      help='output file name [default: prefix of input_file]')
1414    (options, args) = parser.parse_args()
1415
1416    if len(args) == 0:
1417        parser.error('input file is missing')
1418    configfile = args[0]
1419
1420    outputfile = options.output
1421    if not outputfile:
1422        m = re.match('(.*)\.[^.]+$', configfile)
1423        if m:
1424            outputfile = m.group(1)
1425        else:
1426            raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"')
1427
1428    # DeprecationWarning: use ConfigParser directly
1429    config = configparser.SafeConfigParser()
1430    config.read(configfile)
1431
1432    output = open(outputfile, 'w')
1433
1434    print_header(output, configfile)
1435
1436    # First try the 'custom' mode; if it fails assume the query mode.
1437    try:
1438        sections = config.get('custom', 'sections').split(':')
1439    except configparser.NoSectionError:
1440        sections = ['header', 'question', 'edns']
1441
1442    for s in sections:
1443        section_param = get_config_param(s)
1444        (obj, xtables) = (section_param[0](), section_param[1])
1445        if get_config(config, s, obj, xtables):
1446            obj.dump(output)
1447
1448    output.close()
1449