1 # ldd - DNS implementation in Python
2 # Copyright (C) 2006 Fredrik Tolf <fredrik@dolda2000.com>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28 logger = logging.getLogger("ldd.dbzone")
31 def __init__(self, dbdir, dbfile):
32 self.env = bsddb.db.DBEnv()
33 self.env.open(dbdir, bsddb.db.DB_JOINENV | bsddb.db.DB_THREAD)
34 self.db = bsddb.db.DB()
35 self.db.open(dbdir + "/" + dbfile, flags = bsddb.db.DB_THREAD)
37 def create(self, dbdir, dbfile):
38 env = bsddb.db.DBEnv()
39 env.open(dbdir, bsddb.db.DB_CREATE | bsddb.db.DB_EXCL | bsddb.db.DB_INIT_MPOOL | bsddb.db.DB_INIT_CDB | bsddb.db.DB_THREAD)
41 db.open(dbdir + "/" + dbfile, dbtype = bsddb.db.DB_HASH, flags = bsddb.db.DB_CREATE | bsddb.db.DB_EXCL | bsddb.db.DB_THREAD)
44 create = classmethod(create)
50 def decoderecord(self, name, record):
51 set = pickle.loads(record)
54 head = rec.rrhead(name, cur[0])
56 newrr = rec.rr(head, cur[1], data)
57 newrr.setflags(cur[3])
61 def encoderecord(self, rrset):
64 set += [(rr.head.rtype, rr.ttl, rr.data, rr.flags)]
65 return pickle.dumps(set)
67 def lookup(self, name):
68 record = self.db.get(str(name))
71 return self.decoderecord(name, record)
73 def set(self, name, rrset):
74 self.db.put(str(name), self.encoderecord(rrset))
77 def hasname(self, name):
78 record = self.db.get(str(name))
79 return record is not None
81 def rmname(self, name):
83 self.db.delete(str(name))
84 except bsddb.db.DBNotFoundError:
88 def rmrtype(self, name, rtype):
89 if type(rtype) == str:
90 rtype = rec.rtypebyname(rtype)
91 rrset = self.lookup(name)
95 if rr.head.rtype == rtype:
100 def addrr(self, name, rr):
101 rrset = self.lookup(name)
105 self.set(name, rrset)
109 cursor = self.db.cursor()
122 def rootify(rrset, origin):
124 if not rr.head.name.rooted:
125 rr.head.name += origin
126 for dname, dval in rr.data.rdata.items():
127 if isinstance(dval, dn.domainname) and not dval.rooted:
128 rr.data.rdata[dname] += origin
130 class dbhandler(server.handler):
131 def __init__(self, dbdir, dbfile):
132 self.db = dnsdb(dbdir, dbfile)
136 def handle(self, query, pkt, origin):
137 resp = proto.responsefor(pkt)
138 if pkt.opcode == proto.QUERY:
139 rrset = self.db.lookup(query.name)
140 if rrset is None and query.name in origin:
141 rrset = self.db.lookup(query.name - origin)
144 rootify(rrset, origin)
145 resp.anlist = [rr for rr in rrset if rr.head.rtype == query.rtype or rr.head.istype("CNAME")]
147 if pkt.opcode == proto.UPDATE:
148 logger.debug("got DDNS request")
149 if len(pkt.qlist) != 1 or not pkt.qlist[0].istype("SOA"):
150 resp.rescode = proto.FORMERR
152 if pkt.qlist[0].name != origin:
153 resp.rescode = proto.NOTAUTH
156 # Check prerequisites
157 for rr in pkt.anlist:
159 resp.rescode = proto.FORMERR
161 if rr.head.name not in origin:
162 resp.rescode = proto.NOTZONE
164 myname = rr.head.name - origin
165 rrset = self.db.lookup(myname)
166 if rr.head.rclass == rec.CLASSANY:
167 if rr.data is not None:
168 resp.rescode = proto.FORMERR
170 if rr.head.rtype == proto.QTANY:
172 resp.rescode = proto.NXDOMAIN
175 if rrset is not None:
177 if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype:
180 resp.rescode = proto.NXRRSET
182 elif rr.head.rclass == rec.CLASSNONE:
183 if rr.data is not None:
184 resp.rescode = proto.FORMERR
186 if rr.head.rtype == proto.QTANY:
187 if rrset is not None:
188 resp.rescode = proto.YXDOMAIN
191 if rrset is not None:
193 if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype:
194 resp.rescode = proto.YXRRSET
196 elif rr.head.rclass == rec.CLASSIN:
197 if rrset is not None:
199 if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype and rr.data == rr2.data:
202 resp.rescode = proto.NXRRSET
205 resp.rescode = FORMERR
208 # Check for permission
210 resp.rescode = proto.REFUSED
212 if type(self.authkeys) == list:
213 if pkt.tsigctx is None:
214 resp.rescode = proto.REFUSED
216 if pkt.tsigctx.error != 0:
217 resp.rescode = proto.NOTAUTH
219 if pkt.tsigctx.key not in self.authkeys:
220 resp.rescode = proto.REFUSED
222 elif type(self.authkeys) == None:
225 # Do precheck on updates
226 for rr in pkt.aulist:
227 if rr.head.name not in origin:
228 resp.rescode = proto.NOTZONE
230 if rr.head.rclass == rec.CLASSIN:
231 if rr.head.rtype == proto.QTANY or rr.data is None:
232 resp.rescode = proto.FORMERR
234 elif rr.head.rclass == rec.CLASSANY:
235 if rr.data is not None:
236 resp.rescode = proto.FORMERR
238 elif rr.head.rclass == rec.CLASSNONE:
239 if rr.head.rtype == proto.QTANY or rr.ttl != 0 or rr.data is None:
240 resp.rescode = proto.FORMERR
243 resp.rescode = proto.FORMERR
247 for rr in pkt.aulist:
248 myname = rr.head.name - origin
249 if rr.head.rclass == rec.CLASSIN:
250 logger.info("adding rr (%s)", rr)
251 self.db.addrr(myname, rr)
252 elif rr.head.rclass == rec.CLASSANY:
253 if rr.head.rtype == proto.QTANY:
254 logger.info("removing rrset (%s)", rr.head.name)
255 self.db.rmname(myname)
257 logger.info("removing rrset (%s, %s)", rr.head.name, rr.head.rtype)
258 self.db.rmrtype(myname, rr.head.rtype)
259 elif rr.head.rclass == rec.CLASSNONE:
260 logger.info("removing rr (%s)", rr)
261 rrset = self.db.lookup(myname)
263 if rrset is not None:
265 if rr2.head == rr.head and rr2.data == rr.data:
268 self.db.set(myname, rrset)
272 resp.rescode = proto.NOTIMP