sqlite3_ds.py 9.3 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. import sqlite3, re, random
  16. import isc
  17. #define the index of different part of one record
  18. RR_TYPE_INDEX = 5
  19. RR_NAME_INDEX = 2
  20. RR_TTL_INDEX = 4
  21. RR_RDATA_INDEX = 7
  22. class Sqlite3DSError(Exception):
  23. """ Define exceptions."""
  24. pass
  25. def create(cur):
  26. """ Set up schema for a newly created zones/records database.
  27. Arguments:
  28. cur - sqlite3 cursor.
  29. """
  30. cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
  31. cur.execute("INSERT INTO schema_version VALUES (1)")
  32. cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
  33. name STRING NOT NULL COLLATE NOCASE,
  34. rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
  35. dnssec BOOLEAN NOT NULL DEFAULT 0)""")
  36. cur.execute("CREATE INDEX zones_byname ON zones (name)")
  37. cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
  38. zone_id INTEGER NOT NULL,
  39. name STRING NOT NULL COLLATE NOCASE,
  40. rname STRING NOT NULL COLLATE NOCASE,
  41. ttl INTEGER NOT NULL,
  42. rdtype STRING NOT NULL COLLATE NOCASE,
  43. sigtype STRING COLLATE NOCASE,
  44. rdata STRING NOT NULL)""")
  45. cur.execute("CREATE INDEX records_byname ON records (name)")
  46. cur.execute("CREATE INDEX records_byrname ON records (rname)")
  47. cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
  48. zone_id INTEGER NOT NULL,
  49. hash STRING NOT NULL COLLATE NOCASE,
  50. owner STRING NOT NULL COLLATE NOCASE,
  51. ttl INTEGER NOT NULL,
  52. rdtype STRING NOT NULL COLLATE NOCASE,
  53. rdata STRING NOT NULL)""")
  54. cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
  55. def open(dbfile):
  56. """ Open a database, if the database is not yet set up, call create
  57. to do so. It may raise Sqlite3DSError if failed to open sqlite3
  58. database file or find bad database schema version in the database.
  59. Arguments:
  60. dbfile - the filename for the sqlite3 database.
  61. Return sqlite3 connection, sqlite3 cursor.
  62. """
  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. def get_zone_datas(zonename, dbfile):
  81. """ A generator function producing an iterable set of
  82. the records in the zone with the given zone name.
  83. Arguments:
  84. zonename - the zone's origin name.
  85. dbfile - the filename for the sqlite3 database.
  86. """
  87. conn, cur = open(dbfile)
  88. zone_id = get_zoneid(zonename, cur)
  89. cur.execute("SELECT * FROM records WHERE zone_id = ?", [zone_id])
  90. record = cur.fetchone()
  91. while record:
  92. yield record
  93. record = cur.fetchone()
  94. cur.close()
  95. conn.close()
  96. def get_zone_soa(zonename, dbfile):
  97. """Return the soa record of the zone with the given zone name.
  98. If the zone doesn't exist, return None.
  99. Arguments:
  100. zonename - the zone's origin name.
  101. dbfile - the filename for the sqlite3 database.
  102. """
  103. conn, cur = open(dbfile)
  104. id = get_zoneid(zonename, cur)
  105. cur.execute("SELECT * FROM records WHERE zone_id = ? and rdtype = ?", [id, 'SOA'])
  106. datas = cur.fetchone()
  107. cur.close()
  108. conn.close()
  109. return datas
  110. def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
  111. """Return the rrset of the zone with the given zone name, rrset
  112. name and given RR type. If the zone doesn't exist or RR type
  113. doesn't exist, return an empty list.
  114. Arguments:
  115. zonename - the zone's origin name.
  116. rr_name - rr name.
  117. rdtype - RR type.
  118. dbfile - the filename for the sqlite3 database.
  119. """
  120. conn, cur = open(dbfile)
  121. id = get_zoneid(zonename, cur)
  122. cur.execute("SELECT * FROM records WHERE name = ? and zone_id = ? and rdtype = ?",
  123. [rr_name, id, rdtype])
  124. datas = cur.fetchall()
  125. cur.close()
  126. conn.close()
  127. return datas
  128. def get_zones_info(dbfile):
  129. """ Return all the zones' information in the database.
  130. Arguments:
  131. dbfile - the filename for the sqlite3 database.
  132. """
  133. conn, cur = open(dbfile)
  134. cur.execute("SELECT name, rdclass FROM zones")
  135. info = cur.fetchone()
  136. while info:
  137. yield info
  138. info = cur.fetchone()
  139. cur.close()
  140. conn.close()
  141. def get_zoneid(zonename, cur):
  142. """ Get the zone_id for a given zone name.
  143. Arguments:
  144. zonename - the zone's origin name.
  145. cur - sqlite3 cursor.
  146. Return zone id for the given zone name, or an empty string if the
  147. zone is not found.
  148. """
  149. cur.execute("SELECT id FROM zones WHERE name = ?", [zonename])
  150. row = cur.fetchone()
  151. if row:
  152. return row[0]
  153. else:
  154. return ''
  155. def zone_exist(zonename, dbfile):
  156. """ Search for the zone with the given zone name in databse. This
  157. method may throw a Sqlite3DSError exception because its underlying
  158. method open() can throw that exception.
  159. Arguments:
  160. zonename - the zone's origin name.
  161. dbfile - the filename for the sqlite3 database.
  162. Return True if the zone is found, otherwise False.
  163. """
  164. conn, cur = open(dbfile)
  165. zoneid = get_zoneid(zonename, cur)
  166. cur.close()
  167. conn.close()
  168. if zoneid:
  169. return True
  170. return False
  171. def reverse_name(name):
  172. """Reverse the labels of a domain name; for example,
  173. given 'www.example.org.', return 'org.example.www.' This is needed
  174. for DNSSEC sort order.
  175. Arguments:
  176. name - the DNS name will be reversed.
  177. """
  178. new = name.split('.')
  179. new.reverse()
  180. if new[0] == '':
  181. new.pop(0)
  182. return '.'.join(new)+'.'
  183. def load(dbfile, zone, reader):
  184. """ Load a zone into the SQL database.
  185. Arguments:
  186. dbfile - the sqlite3 database filename
  187. zone - the zone origin
  188. reader - a generator function producing an iterable set of
  189. name/ttl/class/rrtype/rdata-text tuples.
  190. """
  191. # if the zone name doesn't contain the trailing dot, automatically add it.
  192. if zone[-1] != '.':
  193. zone += '.'
  194. conn, cur = open(dbfile)
  195. try:
  196. old_zone_id = get_zoneid(zone, cur)
  197. temp = str(random.randrange(100000))
  198. cur.execute("INSERT INTO zones (name, rdclass) VALUES (?, 'IN')", [temp])
  199. new_zone_id = cur.lastrowid
  200. for name, ttl, rdclass, rdtype, rdata in reader():
  201. sigtype = ''
  202. if rdtype.lower() == 'rrsig':
  203. sigtype = rdata.split()[0]
  204. if rdtype.lower() == 'nsec3' or sigtype.lower() == 'nsec3':
  205. hash = name.split('.')[0]
  206. cur.execute("""INSERT INTO nsec3
  207. (zone_id, hash, owner, ttl, rdtype, rdata)
  208. VALUES (?, ?, ?, ?, ?, ?)""",
  209. [new_zone_id, hash, name, ttl, rdtype, rdata])
  210. elif rdtype.lower() == 'rrsig':
  211. cur.execute("""INSERT INTO records
  212. (zone_id, name, rname, ttl,
  213. rdtype, sigtype, rdata)
  214. VALUES (?, ?, ?, ?, ?, ?, ?)""",
  215. [new_zone_id, name, reverse_name(name), ttl,
  216. rdtype, sigtype, rdata])
  217. else:
  218. cur.execute("""INSERT INTO records
  219. (zone_id, name, rname, ttl, rdtype, rdata)
  220. VALUES (?, ?, ?, ?, ?, ?)""",
  221. [new_zone_id, name, reverse_name(name), ttl,
  222. rdtype, rdata])
  223. if old_zone_id:
  224. cur.execute("DELETE FROM zones WHERE id=?", [old_zone_id])
  225. cur.execute("UPDATE zones SET name=? WHERE id=?", [zone, new_zone_id])
  226. conn.commit()
  227. cur.execute("DELETE FROM records WHERE zone_id=?", [old_zone_id])
  228. cur.execute("DELETE FROM nsec3 WHERE zone_id=?", [old_zone_id])
  229. conn.commit()
  230. else:
  231. cur.execute("UPDATE zones SET name=? WHERE id=?", [zone, new_zone_id])
  232. conn.commit()
  233. except Exception as e:
  234. fail = "Error while loading " + zone + ": " + e.args[0]
  235. raise Sqlite3DSError(fail)
  236. finally:
  237. cur.close()
  238. conn.close()