sqlite3_ds.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # Copyright (C) 2010 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. # $Id$
  16. import sqlite3, re, random
  17. import isc
  18. #########################################################################
  19. # define exceptions
  20. #########################################################################
  21. class Sqlite3DSError(Exception):
  22. pass
  23. #########################################################################
  24. # create: set up schema for a newly created zones/records database
  25. #########################################################################
  26. def create(cur):
  27. """Create new zone database"""
  28. cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
  29. cur.execute("INSERT INTO schema_version VALUES (1)")
  30. cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
  31. name STRING NOT NULL COLLATE NOCASE,
  32. rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
  33. dnssec BOOLEAN NOT NULL DEFAULT 0)""")
  34. cur.execute("CREATE INDEX zones_byname ON zones (name)")
  35. cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
  36. zone_id INTEGER NOT NULL,
  37. name STRING NOT NULL COLLATE NOCASE,
  38. rname STRING NOT NULL COLLATE NOCASE,
  39. ttl INTEGER NOT NULL,
  40. rdtype STRING NOT NULL COLLATE NOCASE,
  41. sigtype STRING COLLATE NOCASE,
  42. rdata STRING NOT NULL)""")
  43. cur.execute("CREATE INDEX records_byname ON records (name)")
  44. cur.execute("CREATE INDEX records_byrname ON records (rname)")
  45. cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
  46. zone_id INTEGER NOT NULL,
  47. hash STRING NOT NULL COLLATE NOCASE,
  48. owner STRING NOT NULL COLLATE NOCASE,
  49. ttl INTEGER NOT NULL,
  50. rdtype STRING NOT NULL COLLATE NOCASE,
  51. rdata STRING NOT NULL)""")
  52. cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
  53. #########################################################################
  54. # open: open a database. if the database is not yet set up,
  55. # call create to do so.
  56. # input:
  57. # dbfile - the filename for the sqlite3 database
  58. # returns:
  59. # sqlite3 connection, sqlite3 cursor
  60. #########################################################################
  61. def open(dbfile):
  62. """Open the database file. If necessary, set it up"""
  63. try:
  64. conn = sqlite3.connect(dbfile)
  65. cur = conn.cursor()
  66. except Exception as e:
  67. fail = "Failed to open " + dbfile + ": " + e.args[0]
  68. raise Sqlite3DSError(fail)
  69. # Does the database exist yet? If not, create it.
  70. try:
  71. cur.execute("SELECT version FROM schema_version")
  72. row = cur.fetchone()
  73. except:
  74. create(cur)
  75. conn.commit()
  76. row = [1]
  77. if row == None or row[0] != 1:
  78. raise Sqlite3DSError("Bad database schema version")
  79. return conn, cur
  80. #########################################################################
  81. # get_zone_datas
  82. # returns all the records for one zone with the given zone name.
  83. #########################################################################
  84. def get_zone_datas(zonename, dbfile):
  85. conn, cur = open(dbfile)
  86. zone_id = get_zoneid(zonename, cur)
  87. cur.execute("SELECT * FROM records WHERE zone_id = ?", [zone_id])
  88. record = cur.fetchone()
  89. while record:
  90. yield record
  91. record = cur.fetchone()
  92. cur.close()
  93. conn.close()
  94. #########################################################################
  95. # get_zone_soa
  96. # returns the soa record of the zone with the given zone name.
  97. # If the zone doesn't exist, return None.
  98. #########################################################################
  99. def get_zone_soa(zonename, dbfile):
  100. conn, cur = open(dbfile)
  101. id = get_zoneid(zonename, cur)
  102. cur.execute("SELECT * FROM records WHERE zone_id = ? and rdtype = ?", [id, 'SOA'])
  103. datas = cur.fetchone()
  104. cur.close()
  105. conn.close()
  106. return datas
  107. #########################################################################
  108. # get_zoneid:
  109. # returns the zone_id for a given zone name, or an empty
  110. # string if the zone is not found
  111. #########################################################################
  112. def get_zoneid(zone, cur):
  113. cur.execute("SELECT id FROM zones WHERE name = ?", [zone])
  114. row = cur.fetchone()
  115. if row:
  116. return row[0]
  117. else:
  118. return ''
  119. #########################################################################
  120. # reverse_name:
  121. # reverse the labels of a DNS name. (for example,
  122. # "bind10.isc.org." would become "org.isc.bind10.")
  123. #########################################################################
  124. def reverse_name(name):
  125. """Reverse the labels of a domain name; for example,
  126. given 'www.isc.org.', return 'org.isc.www.' This is needed
  127. for DNSSEC sort order."""
  128. new = name.split('.')
  129. new.reverse()
  130. if new[0] == '':
  131. new.pop(0)
  132. return '.'.join(new)+'.'
  133. #########################################################################
  134. # load:
  135. # load a zone into the SQL database.
  136. # input:
  137. # dbfile: the sqlite3 database fileanme
  138. # zone: the zone origin
  139. # reader: an generator function producing an iterable set of
  140. # name/ttl/class/rrtype/rdata-text tuples
  141. #########################################################################
  142. def load(dbfile, zone, reader):
  143. conn, cur = open(dbfile)
  144. old_zone_id = get_zoneid(zone, cur)
  145. temp = str(random.randrange(100000))
  146. cur.execute("INSERT INTO zones (name, rdclass) VALUES (?, 'IN')", [temp])
  147. new_zone_id = cur.lastrowid
  148. try:
  149. for name, ttl, rdclass, rdtype, rdata in reader():
  150. sigtype = ''
  151. if rdtype.lower() == 'rrsig':
  152. sigtype = rdata.split()[0]
  153. if rdtype.lower() == 'nsec3' or sigtype.lower() == 'nsec3':
  154. hash = name.split('.')[0]
  155. cur.execute("""INSERT INTO nsec3
  156. (zone_id, hash, owner, ttl, rdtype, rdata)
  157. VALUES (?, ?, ?, ?, ?, ?)""",
  158. [new_zone_id, hash, name, ttl, rdtype, rdata])
  159. elif rdtype.lower() == 'rrsig':
  160. cur.execute("""INSERT INTO records
  161. (zone_id, name, rname, ttl,
  162. rdtype, sigtype, rdata)
  163. VALUES (?, ?, ?, ?, ?, ?, ?)""",
  164. [new_zone_id, name, reverse_name(name), ttl,
  165. rdtype, sigtype, rdata])
  166. else:
  167. cur.execute("""INSERT INTO records
  168. (zone_id, name, rname, ttl, rdtype, rdata)
  169. VALUES (?, ?, ?, ?, ?, ?)""",
  170. [new_zone_id, name, reverse_name(name), ttl,
  171. rdtype, rdata])
  172. except Exception as e:
  173. fail = "Error while loading " + zone + ": " + e.args[0]
  174. raise Sqlite3DSError(fail)
  175. if old_zone_id:
  176. cur.execute("DELETE FROM zones WHERE id=?", [old_zone_id])
  177. cur.execute("UPDATE zones SET name=? WHERE id=?", [zone, new_zone_id])
  178. conn.commit()
  179. cur.execute("DELETE FROM records WHERE zone_id=?", [old_zone_id])
  180. cur.execute("DELETE FROM nsec3 WHERE zone_id=?", [old_zone_id])
  181. conn.commit()
  182. else:
  183. cur.execute("UPDATE zones SET name=? WHERE id=?", [zone, new_zone_id])
  184. conn.commit()
  185. cur.close()
  186. conn.close()
  187. #########################################################################
  188. # temp sqlite3 datasource backend for axfr in. The code should be refectored
  189. # later.
  190. #########################################################################
  191. class AXFRInDB:
  192. def __init__(self, dbfile, zone_name):
  193. self._dbfile = dbfile
  194. self._zone_name = zone_name
  195. # if the zone name doesn't contain the trailing dot, automatically
  196. # add it.
  197. if self._zone_name[-1] != '.':
  198. self._zone_name += '.'
  199. self._old_zone_id = None
  200. self._new_zone_id = None
  201. def prepare_axfrin(self):
  202. self._conn, self._cur = open(self._dbfile)
  203. self._old_zone_id = get_zoneid(self._zone_name, self._cur)
  204. temp = str(random.randrange(100000))
  205. self._cur.execute("INSERT INTO zones (name, rdclass) VALUES (?, 'IN')", [temp])
  206. self._new_zone_id = self._cur.lastrowid
  207. def insert_axfr_record(self, rrsets):
  208. '''insert zone records to sqlite3 database'''
  209. try:
  210. for name, ttl, rdclass, rdtype, rdata in rrsets:
  211. sigtype = ''
  212. if rdtype.lower() == 'rrsig':
  213. sigtype = rdata.split()[0]
  214. if rdtype.lower() == 'nsec3' or sigtype.lower() == 'nsec3':
  215. hash = name.split('.')[0]
  216. self._cur.execute("""INSERT INTO nsec3
  217. (zone_id, hash, owner, ttl, rdtype, rdata)
  218. VALUES (?, ?, ?, ?, ?, ?)""",
  219. [self._new_zone_id, hash, name, ttl, rdtype, rdata])
  220. elif rdtype.lower() == 'rrsig':
  221. self._cur.execute("""INSERT INTO records
  222. (zone_id, name, rname, ttl,
  223. rdtype, sigtype, rdata)
  224. VALUES (?, ?, ?, ?, ?, ?, ?)""",
  225. [self._new_zone_id, name, reverse_name(name), ttl,
  226. rdtype, sigtype, rdata])
  227. else:
  228. self._cur.execute("""INSERT INTO records
  229. (zone_id, name, rname, ttl, rdtype, rdata)
  230. VALUES (?, ?, ?, ?, ?, ?)""",
  231. [self._new_zone_id, name, reverse_name(name), ttl,
  232. rdtype, rdata])
  233. except Exception as e:
  234. fail = "Error while loading " + self._zone_name + ": " + e.args[0]
  235. raise Sqlite3DSError(fail)
  236. def finish_axfrin(self):
  237. '''commit changes and close sqlite3 database'''
  238. if self._old_zone_id:
  239. self._cur.execute("DELETE FROM zones WHERE id=?", [self._old_zone_id])
  240. self._cur.execute("UPDATE zones SET name=? WHERE id=?", [self._zone_name, self._new_zone_id])
  241. self._conn.commit()
  242. self._cur.execute("DELETE FROM records WHERE zone_id=?", [self._old_zone_id])
  243. self._cur.execute("DELETE FROM nsec3 WHERE zone_id=?", [self._old_zone_id])
  244. self._conn.commit()
  245. else:
  246. self._cur.execute("UPDATE zones SET name=? WHERE id=?", [self._zone_name, self._new_zone_id])
  247. self._conn.commit()
  248. self._cur.close()
  249. self._conn.close()