gen-wiredata.py.in 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. #!@PYTHON@
  2. # Copyright (C) 2010 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. import configparser, re, time, socket, sys
  17. from datetime import datetime
  18. from optparse import OptionParser
  19. re_hex = re.compile(r'^0x[0-9a-fA-F]+')
  20. re_decimal = re.compile(r'^\d+$')
  21. re_string = re.compile(r"\'(.*)\'$")
  22. dnssec_timefmt = '%Y%m%d%H%M%S'
  23. dict_qr = { 'query' : 0, 'response' : 1 }
  24. dict_opcode = { 'query' : 0, 'iquery' : 1, 'status' : 2, 'notify' : 4,
  25. 'update' : 5 }
  26. rdict_opcode = dict([(dict_opcode[k], k.upper()) for k in dict_opcode.keys()])
  27. dict_rcode = { 'noerror' : 0, 'formerr' : 1, 'servfail' : 2, 'nxdomain' : 3,
  28. 'notimp' : 4, 'refused' : 5, 'yxdomain' : 6, 'yxrrset' : 7,
  29. 'nxrrset' : 8, 'notauth' : 9, 'notzone' : 10 }
  30. rdict_rcode = dict([(dict_rcode[k], k.upper()) for k in dict_rcode.keys()])
  31. dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
  32. 'soa' : 6, 'mb' : 7, 'mg' : 8, 'mr' : 9, 'null' : 10,
  33. 'wks' : 11, 'ptr' : 12, 'hinfo' : 13, 'minfo' : 14, 'mx' : 15,
  34. 'txt' : 16, 'rp' : 17, 'afsdb' : 18, 'x25' : 19, 'isdn' : 20,
  35. 'rt' : 21, 'nsap' : 22, 'nsap_tr' : 23, 'sig' : 24, 'key' : 25,
  36. 'px' : 26, 'gpos' : 27, 'aaaa' : 28, 'loc' : 29, 'nxt' : 30,
  37. 'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
  38. 'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
  39. 'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
  40. 'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'hip' : 55,
  41. 'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
  42. 'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
  43. 'maila' : 254, 'any' : 255 }
  44. rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
  45. dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
  46. rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
  47. dict_rrclass.keys()])
  48. dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
  49. 'rsasha1' : 5 }
  50. dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
  51. rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
  52. dict_algorithm.keys()])
  53. rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
  54. dict_nsec3_algorithm.keys()])
  55. header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
  56. 'rcode' : dict_rcode }
  57. question_xtables = { 'rrtype' : dict_rrtype, 'rrclass' : dict_rrclass }
  58. rrsig_xtables = { 'algorithm' : dict_algorithm }
  59. def parse_value(value, xtable = {}):
  60. if re.search(re_hex, value):
  61. return int(value, 16)
  62. if re.search(re_decimal, value):
  63. return int(value)
  64. m = re.match(re_string, value)
  65. if m:
  66. return m.group(1)
  67. lovalue = value.lower()
  68. if lovalue in xtable:
  69. return xtable[lovalue]
  70. return value
  71. def code_totext(code, dict):
  72. if code in dict.keys():
  73. return dict[code] + '(' + str(code) + ')'
  74. return str(code)
  75. def encode_name(name, absolute=True):
  76. # make sure the name is dot-terminated. duplicate dots will be ignored
  77. # below.
  78. name += '.'
  79. labels = name.split('.')
  80. wire = ''
  81. for l in labels:
  82. if len(l) > 4 and l[0:4] == 'ptr=':
  83. # special meta-syntax for compression pointer
  84. wire += '%04x' % (0xc000 | int(l[4:]))
  85. break
  86. if absolute or len(l) > 0:
  87. wire += '%02x' % len(l)
  88. wire += ''.join(['%02x' % ord(ch) for ch in l])
  89. if len(l) == 0:
  90. break
  91. return wire
  92. def encode_string(name, len=None):
  93. if type(name) is int and len is not None:
  94. return '%0.*x' % (len * 2, name)
  95. return ''.join(['%02x' % ord(ch) for ch in name])
  96. def count_namelabels(name):
  97. if name == '.': # special case
  98. return 0
  99. m = re.match('^(.*)\.$', name)
  100. if m:
  101. name = m.group(1)
  102. return len(name.split('.'))
  103. def get_config(config, section, configobj, xtables = {}):
  104. try:
  105. for field in config.options(section):
  106. value = config.get(section, field)
  107. if field in xtables.keys():
  108. xtable = xtables[field]
  109. else:
  110. xtable = {}
  111. configobj.__dict__[field] = parse_value(value, xtable)
  112. except configparser.NoSectionError:
  113. return False
  114. return True
  115. def print_header(f, input_file):
  116. f.write('''###
  117. ### This data file was auto-generated from ''' + input_file + '''
  118. ###
  119. ''')
  120. class Name:
  121. name = 'example.com'
  122. pointer = None # no compression by default
  123. def dump(self, f):
  124. name = self.name
  125. if self.pointer is not None:
  126. if len(name) > 0 and name[-1] != '.':
  127. name += '.'
  128. name += 'ptr=%d' % self.pointer
  129. name_wire = encode_name(name)
  130. f.write('\n# DNS Name: %s' % self.name)
  131. if self.pointer is not None:
  132. f.write(' + compression pointer: %d' % self.pointer)
  133. f.write('\n')
  134. f.write('%s' % name_wire)
  135. f.write('\n')
  136. class DNSHeader:
  137. id = 0x1035
  138. (qr, aa, tc, rd, ra, ad, cd) = 0, 0, 0, 0, 0, 0, 0
  139. mbz = 0
  140. rcode = 0 # noerror
  141. opcode = 0 # query
  142. (qdcount, ancount, nscount, arcount) = 1, 0, 0, 0
  143. def dump(self, f):
  144. f.write('\n# Header Section\n')
  145. f.write('# ID=' + str(self.id))
  146. f.write(' QR=' + ('Response' if self.qr else 'Query'))
  147. f.write(' Opcode=' + code_totext(self.opcode, rdict_opcode))
  148. f.write(' Rcode=' + code_totext(self.rcode, rdict_rcode))
  149. f.write('%s' % (' AA' if self.aa else ''))
  150. f.write('%s' % (' TC' if self.tc else ''))
  151. f.write('%s' % (' RD' if self.rd else ''))
  152. f.write('%s' % (' AD' if self.ad else ''))
  153. f.write('%s' % (' CD' if self.cd else ''))
  154. f.write('\n')
  155. f.write('%04x ' % self.id)
  156. flag_and_code = 0
  157. flag_and_code |= (self.qr << 15 | self.opcode << 14 | self.aa << 10 |
  158. self.tc << 9 | self.rd << 8 | self.ra << 7 |
  159. self.mbz << 6 | self.ad << 5 | self.cd << 4 |
  160. self.rcode)
  161. f.write('%04x\n' % flag_and_code)
  162. f.write('# QDCNT=%d, ANCNT=%d, NSCNT=%d, ARCNT=%d\n' %
  163. (self.qdcount, self.ancount, self.nscount, self.arcount))
  164. f.write('%04x %04x %04x %04x\n' % (self.qdcount, self.ancount,
  165. self.nscount, self.arcount))
  166. class DNSQuestion:
  167. name = 'example.com.'
  168. rrtype = parse_value('A', dict_rrtype)
  169. rrclass = parse_value('IN', dict_rrclass)
  170. def dump(self, f):
  171. f.write('\n# Question Section\n')
  172. f.write('# QNAME=%s QTYPE=%s QCLASS=%s\n' %
  173. (self.name,
  174. code_totext(self.rrtype, rdict_rrtype),
  175. code_totext(self.rrclass, rdict_rrclass)))
  176. f.write(encode_name(self.name))
  177. f.write(' %04x %04x\n' % (self.rrtype, self.rrclass))
  178. class EDNS:
  179. name = '.'
  180. udpsize = 4096
  181. extrcode = 0
  182. version = 0
  183. do = 0
  184. mbz = 0
  185. rdlen = 0
  186. def dump(self, f):
  187. f.write('\n# EDNS OPT RR\n')
  188. f.write('# NAME=%s TYPE=%s UDPSize=%d ExtRcode=%s Version=%s DO=%d\n' %
  189. (self.name, code_totext(dict_rrtype['opt'], rdict_rrtype),
  190. self.udpsize, self.extrcode, self.version,
  191. 1 if self.do else 0))
  192. code_vers = (self.extrcode << 8) | (self.version & 0x00ff)
  193. extflags = (self.do << 15) | (self.mbz & 0x8000)
  194. f.write('%s %04x %04x %04x %04x\n' %
  195. (encode_name(self.name), dict_rrtype['opt'], self.udpsize,
  196. code_vers, extflags))
  197. f.write('# RDLEN=%d\n' % self.rdlen)
  198. f.write('%04x\n' % self.rdlen)
  199. class RR:
  200. '''This is a base class for various types of RR test data.
  201. For each RR type (A, AAAA, NS, etc), we define a derived class of RR
  202. to dump type specific RDATA parameters. This class defines parameters
  203. common to all types of RDATA, namely the owner name, RR class and TTL.
  204. The dump() method of derived classes are expected to call dump_header(),
  205. whose default implementation is provided in this class. This method
  206. decides whether to dump the test data as an RR (with name, type, class)
  207. or only as RDATA (with its length), and dumps the corresponding data
  208. via the specified file object.
  209. By convention we assume derived classes are named after the common
  210. standard mnemonic of the corresponding RR types. For example, the
  211. derived class for the RR type SOA should be named "SOA".
  212. Configurable parameters are as follows:
  213. - as_rr (bool): Whether or not the data is to be dumped as an RR. False
  214. by default.
  215. - rr_class (string): The RR class of the data. Only meaningful when the
  216. data is dumped as an RR. Default is 'IN'.
  217. - rr_ttl (integer): The TTL value of the RR. Only meaningful when the
  218. data is dumped as an RR. Default is 86400 (1 day).
  219. '''
  220. def __init__(self):
  221. self.as_rr = False
  222. # only when as_rr is True, same for class/TTL:
  223. self.rr_name = 'example.com'
  224. self.rr_class = 'IN'
  225. self.rr_ttl = 86400
  226. def dump_header(self, f, rdlen):
  227. type_txt = self.__class__.__name__
  228. type_code = parse_value(type_txt, dict_rrtype)
  229. if self.as_rr:
  230. rrclass = parse_value(self.rr_class, dict_rrclass)
  231. f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d RDLEN=%d)\n' %
  232. (type_txt, self.rr_name,
  233. code_totext(rrclass, rdict_rrclass), self.rr_ttl, rdlen))
  234. f.write('%s %04x %04x %08x %04x\n' %
  235. (encode_name(self.rr_name), type_code, rrclass,
  236. self.rr_ttl, rdlen))
  237. else:
  238. f.write('\n# %s RDATA (RDLEN=%d)\n' % (type_txt, rdlen))
  239. f.write('%04x\n' % rdlen)
  240. class A(RR):
  241. rdlen = 4 # fixed by default
  242. address = '192.0.2.1'
  243. def dump(self, f):
  244. self.dump_header(f, self.rdlen)
  245. f.write('# Address=%s\n' % (self.address))
  246. bin_address = socket.inet_aton(self.address)
  247. f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
  248. bin_address[2], bin_address[3]))
  249. class NS(RR):
  250. rdlen = None # auto calculate
  251. nsname = 'ns.example.com'
  252. def dump(self, f):
  253. nsname_wire = encode_name(self.nsname)
  254. if self.rdlen is None:
  255. self.rdlen = len(nsname_wire) / 2
  256. self.dump_header(f, self.rdlen)
  257. f.write('# NS name=%s\n' % (self.nsname))
  258. f.write('%s\n' % nsname_wire)
  259. class SOA:
  260. # this currently doesn't support name compression within the RDATA.
  261. rdlen = -1 # auto-calculate
  262. mname = 'ns.example.com'
  263. rname = 'root.example.com'
  264. serial = 2010012601
  265. refresh = 3600
  266. retry = 300
  267. expire = 3600000
  268. minimum = 1200
  269. def dump(self, f):
  270. mname_wire = encode_name(self.mname)
  271. rname_wire = encode_name(self.rname)
  272. rdlen = self.rdlen
  273. if rdlen < 0:
  274. rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
  275. f.write('\n# SOA RDATA (RDLEN=%d)\n' % rdlen)
  276. f.write('%04x\n' % rdlen);
  277. f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
  278. f.write('%s %s\n' % (mname_wire, rname_wire))
  279. f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
  280. (self.serial, self.refresh, self.retry, self.expire,
  281. self.minimum))
  282. f.write('%08x %08x %08x %08x %08x\n' % (self.serial, self.refresh,
  283. self.retry, self.expire,
  284. self.minimum))
  285. class TXT:
  286. rdlen = -1 # auto-calculate
  287. nstring = 1 # number of character-strings
  288. stringlen = -1 # default string length, auto-calculate
  289. string = 'Test String' # default string
  290. def dump(self, f):
  291. stringlen_list = []
  292. string_list = []
  293. wirestring_list = []
  294. for i in range(0, self.nstring):
  295. key_string = 'string' + str(i)
  296. if key_string in self.__dict__:
  297. string_list.append(self.__dict__[key_string])
  298. else:
  299. string_list.append(self.string)
  300. wirestring_list.append(encode_string(string_list[-1]))
  301. key_stringlen = 'stringlen' + str(i)
  302. if key_stringlen in self.__dict__:
  303. stringlen_list.append(self.__dict__[key_stringlen])
  304. else:
  305. stringlen_list.append(self.stringlen)
  306. if stringlen_list[-1] < 0:
  307. stringlen_list[-1] = int(len(wirestring_list[-1]) / 2)
  308. rdlen = self.rdlen
  309. if rdlen < 0:
  310. rdlen = int(len(''.join(wirestring_list)) / 2) + self.nstring
  311. f.write('\n# TXT RDATA (RDLEN=%d)\n' % rdlen)
  312. f.write('%04x\n' % rdlen);
  313. for i in range(0, self.nstring):
  314. f.write('# String Len=%d, String=\"%s\"\n' %
  315. (stringlen_list[i], string_list[i]))
  316. f.write('%02x%s%s\n' % (stringlen_list[i],
  317. ' ' if len(wirestring_list[i]) > 0 else '',
  318. wirestring_list[i]))
  319. class RP:
  320. '''Implements rendering RP RDATA in the wire format.
  321. Configurable parameters are as follows:
  322. - rdlen: 16-bit RDATA length. If omitted, the accurate value is auto
  323. calculated and used; if negative, the RDLEN field will be omitted from
  324. the output data.
  325. - mailbox: The mailbox field.
  326. - text: The text field.
  327. All of these parameters have the default values and can be omitted.
  328. '''
  329. rdlen = None # auto-calculate
  330. mailbox = 'root.example.com'
  331. text = 'rp-text.example.com'
  332. def dump(self, f):
  333. mailbox_wire = encode_name(self.mailbox)
  334. text_wire = encode_name(self.text)
  335. if self.rdlen is None:
  336. self.rdlen = (len(mailbox_wire) + len(text_wire)) / 2
  337. else:
  338. self.rdlen = int(self.rdlen)
  339. if self.rdlen >= 0:
  340. f.write('\n# RP RDATA (RDLEN=%d)\n' % self.rdlen)
  341. f.write('%04x\n' % self.rdlen)
  342. else:
  343. f.write('\n# RP RDATA (RDLEN omitted)\n')
  344. f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
  345. f.write('%s %s\n' % (mailbox_wire, text_wire))
  346. class NSECBASE:
  347. '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
  348. these RRs. The NSEC and NSEC3 classes will be inherited from this
  349. class.'''
  350. nbitmap = 1 # number of bitmaps
  351. block = 0
  352. maplen = None # default bitmap length, auto-calculate
  353. bitmap = '040000000003' # an arbtrarily chosen bitmap sample
  354. def dump(self, f):
  355. # first, construct the bitmpa data
  356. block_list = []
  357. maplen_list = []
  358. bitmap_list = []
  359. for i in range(0, self.nbitmap):
  360. key_bitmap = 'bitmap' + str(i)
  361. if key_bitmap in self.__dict__:
  362. bitmap_list.append(self.__dict__[key_bitmap])
  363. else:
  364. bitmap_list.append(self.bitmap)
  365. key_maplen = 'maplen' + str(i)
  366. if key_maplen in self.__dict__:
  367. maplen_list.append(self.__dict__[key_maplen])
  368. else:
  369. maplen_list.append(self.maplen)
  370. if maplen_list[-1] is None: # calculate it if not specified
  371. maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
  372. key_block = 'block' + str(i)
  373. if key_block in self.__dict__:
  374. block_list.append(self.__dict__[key_block])
  375. else:
  376. block_list.append(self.block)
  377. # dump RR-type specific part (NSEC or NSEC3)
  378. self.dump_fixedpart(f, 2 * self.nbitmap + \
  379. int(len(''.join(bitmap_list)) / 2))
  380. # dump the bitmap
  381. for i in range(0, self.nbitmap):
  382. f.write('# Bitmap: Block=%d, Length=%d\n' %
  383. (block_list[i], maplen_list[i]))
  384. f.write('%02x %02x %s\n' %
  385. (block_list[i], maplen_list[i], bitmap_list[i]))
  386. class NSEC(NSECBASE):
  387. rdlen = None # auto-calculate
  388. nextname = 'next.example.com'
  389. def dump_fixedpart(self, f, bitmap_totallen):
  390. name_wire = encode_name(self.nextname)
  391. if self.rdlen is None:
  392. # if rdlen needs to be calculated, it must be based on the bitmap
  393. # length, because the configured maplen can be fake.
  394. self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
  395. f.write('\n# NSEC RDATA (RDLEN=%d)\n' % self.rdlen)
  396. f.write('%04x\n' % self.rdlen);
  397. f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
  398. int(len(name_wire) / 2)))
  399. f.write('%s\n' % name_wire)
  400. class NSEC3(NSECBASE):
  401. rdlen = None # auto-calculate
  402. hashalg = 1 # SHA-1
  403. optout = False # opt-out flag
  404. mbz = 0 # other flag fields (none defined yet)
  405. iterations = 1
  406. saltlen = 5
  407. salt = 's' * saltlen
  408. hashlen = 20
  409. hash = 'h' * hashlen
  410. def dump_fixedpart(self, f, bitmap_totallen):
  411. if self.rdlen is None:
  412. # if rdlen needs to be calculated, it must be based on the bitmap
  413. # length, because the configured maplen can be fake.
  414. self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
  415. + bitmap_totallen
  416. f.write('\n# NSEC3 RDATA (RDLEN=%d)\n' % self.rdlen)
  417. f.write('%04x\n' % self.rdlen)
  418. optout_val = 1 if self.optout else 0
  419. f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
  420. (code_totext(self.hashalg, rdict_nsec3_algorithm),
  421. optout_val, self.mbz, self.iterations))
  422. f.write('%02x %02x %04x\n' %
  423. (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
  424. f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
  425. f.write('%02x%s%s\n' % (self.saltlen,
  426. ' ' if len(self.salt) > 0 else '',
  427. encode_string(self.salt)))
  428. f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
  429. f.write('%02x%s%s\n' % (self.hashlen,
  430. ' ' if len(self.hash) > 0 else '',
  431. encode_string(self.hash)))
  432. class RRSIG:
  433. rdlen = -1 # auto-calculate
  434. covered = 1 # A
  435. algorithm = 5 # RSA-SHA1
  436. labels = -1 # auto-calculate (#labels of signer)
  437. originalttl = 3600
  438. expiration = int(time.mktime(datetime.strptime('20100131120000',
  439. dnssec_timefmt).timetuple()))
  440. inception = int(time.mktime(datetime.strptime('20100101120000',
  441. dnssec_timefmt).timetuple()))
  442. tag = 0x1035
  443. signer = 'example.com'
  444. signature = 0x123456789abcdef123456789abcdef
  445. def dump(self, f):
  446. name_wire = encode_name(self.signer)
  447. sig_wire = '%x' % self.signature
  448. rdlen = self.rdlen
  449. if rdlen < 0:
  450. rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2)
  451. labels = self.labels
  452. if labels < 0:
  453. labels = count_namelabels(self.signer)
  454. f.write('\n# RRSIG RDATA (RDLEN=%d)\n' % rdlen)
  455. f.write('%04x\n' % rdlen);
  456. f.write('# Covered=%s Algorithm=%s Labels=%d OrigTTL=%d\n' %
  457. (code_totext(self.covered, rdict_rrtype),
  458. code_totext(self.algorithm, rdict_algorithm), labels,
  459. self.originalttl))
  460. f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
  461. labels, self.originalttl))
  462. f.write('# Expiration=%s, Inception=%s\n' %
  463. (str(self.expiration), str(self.inception)))
  464. f.write('%08x %08x\n' % (self.expiration, self.inception))
  465. f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
  466. f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
  467. class TSIG(RR):
  468. rdlen = None # auto-calculate
  469. algorithm = 'hmac-sha256'
  470. time_signed = 1286978795 # arbitrarily chosen default
  471. fudge = 300
  472. mac_size = None # use a common value for the algorithm
  473. mac = None # use 'x' * mac_size
  474. original_id = 2845 # arbitrarily chosen default
  475. error = 0
  476. other_len = None # 6 if error is BADTIME; otherwise 0
  477. other_data = None # use time_signed + fudge + 1 for BADTIME
  478. dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
  479. # TSIG has some special defaults
  480. def __init__(self):
  481. self.rr_class = 'ANY'
  482. self.rr_ttl = 0
  483. def dump(self, f):
  484. if str(self.algorithm) == 'hmac-md5':
  485. name_wire = encode_name('hmac-md5.sig-alg.reg.int')
  486. else:
  487. name_wire = encode_name(self.algorithm)
  488. mac_size = self.mac_size
  489. if mac_size is None:
  490. if self.algorithm in self.dict_macsize.keys():
  491. mac_size = self.dict_macsize[self.algorithm]
  492. else:
  493. raise RuntimeError('TSIG Mac Size cannot be determined')
  494. mac = encode_string('x' * mac_size) if self.mac is None else \
  495. encode_string(self.mac, mac_size)
  496. other_len = self.other_len
  497. if other_len is None:
  498. # 18 = BADTIME
  499. other_len = 6 if self.error == 18 else 0
  500. other_data = self.other_data
  501. if other_data is None:
  502. other_data = '%012x' % (self.time_signed + self.fudge + 1) \
  503. if self.error == 18 else ''
  504. else:
  505. other_data = encode_string(self.other_data, other_len)
  506. if self.rdlen is None:
  507. self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
  508. len(other_data) / 2)
  509. self.dump_header(f, self.rdlen)
  510. f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
  511. (self.algorithm, self.time_signed, self.fudge))
  512. f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
  513. f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
  514. f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
  515. f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
  516. f.write('%04x %04x\n' % (self.original_id, self.error))
  517. f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
  518. f.write('%04x%s\n' % (other_len,
  519. ' ' + other_data if len(other_data) > 0 else ''))
  520. def get_config_param(section):
  521. config_param = {'name' : (Name, {}),
  522. 'header' : (DNSHeader, header_xtables),
  523. 'question' : (DNSQuestion, question_xtables),
  524. 'edns' : (EDNS, {}), 'a' : (A, {}), 'ns' : (NS, {}),
  525. 'soa' : (SOA, {}), 'txt' : (TXT, {}),
  526. 'rp' : (RP, {}), 'rrsig' : (RRSIG, {}),
  527. 'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
  528. 'tsig' : (TSIG, {}) }
  529. s = section
  530. m = re.match('^([^:]+)/\d+$', section)
  531. if m:
  532. s = m.group(1)
  533. return config_param[s]
  534. usage = '''usage: %prog [options] input_file'''
  535. if __name__ == "__main__":
  536. parser = OptionParser(usage=usage)
  537. parser.add_option('-o', '--output', action='store', dest='output',
  538. default=None, metavar='FILE',
  539. help='output file name [default: prefix of input_file]')
  540. (options, args) = parser.parse_args()
  541. if len(args) == 0:
  542. parser.error('input file is missing')
  543. configfile = args[0]
  544. outputfile = options.output
  545. if not outputfile:
  546. m = re.match('(.*)\.[^.]+$', configfile)
  547. if m:
  548. outputfile = m.group(1)
  549. else:
  550. raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"')
  551. config = configparser.SafeConfigParser()
  552. config.read(configfile)
  553. output = open(outputfile, 'w')
  554. print_header(output, configfile)
  555. # First try the 'custom' mode; if it fails assume the standard mode.
  556. try:
  557. sections = config.get('custom', 'sections').split(':')
  558. except configparser.NoSectionError:
  559. sections = ['header', 'question', 'edns']
  560. for s in sections:
  561. section_param = get_config_param(s)
  562. (obj, xtables) = (section_param[0](), section_param[1])
  563. if get_config(config, s, obj, xtables):
  564. obj.dump(output)
  565. output.close()