X-Git-Url: http://dolda2000.com/gitweb/?p=ldd.git;a=blobdiff_plain;f=ldd%2Fdbzone.py;fp=ldd%2Fdbzone.py;h=7cce85d1373e9fbf68fba290561707e85afb9ac6;hp=0000000000000000000000000000000000000000;hb=769e7ed964e3720cf25825dd5390af5fb0bf4851;hpb=2e783944bffb349dff8667dab0ba0c48b21c9504 diff --git a/ldd/dbzone.py b/ldd/dbzone.py new file mode 100644 index 0000000..7cce85d --- /dev/null +++ b/ldd/dbzone.py @@ -0,0 +1,273 @@ +# ldd - DNS implementation in Python +# Copyright (C) 2006 Fredrik Tolf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import bsddb +import threading +import pickle +import logging + +import server +import proto +import rec +import dn + +logger = logging.getLogger("ldd.dbzone") + +class dnsdb: + def __init__(self, dbdir, dbfile): + self.env = bsddb.db.DBEnv() + self.env.open(dbdir, bsddb.db.DB_JOINENV | bsddb.db.DB_THREAD) + self.db = bsddb.db.DB() + self.db.open(dbdir + "/" + dbfile, flags = bsddb.db.DB_THREAD) + + def create(self, dbdir, dbfile): + env = bsddb.db.DBEnv() + 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) + db = bsddb.db.DB() + db.open(dbdir + "/" + dbfile, dbtype = bsddb.db.DB_HASH, flags = bsddb.db.DB_CREATE | bsddb.db.DB_EXCL | bsddb.db.DB_THREAD) + db.close() + env.close() + create = classmethod(create) + + def close(self): + self.db.close() + self.env.close() + + def decoderecord(self, name, record): + set = pickle.loads(record) + rrset = [] + for cur in set: + head = rec.rrhead(name, cur[0]) + data = cur[2] + newrr = rec.rr(head, cur[1], data) + newrr.setflags(cur[3]) + rrset += [newrr] + return rrset + + def encoderecord(self, rrset): + set = [] + for rr in rrset: + set += [(rr.head.rtype, rr.ttl, rr.data, rr.flags)] + return pickle.dumps(set) + + def lookup(self, name): + record = self.db.get(str(name)) + if record is None: + return None + return self.decoderecord(name, record) + + def set(self, name, rrset): + self.db.put(str(name), self.encoderecord(rrset)) + return True + + def hasname(self, name): + record = self.db.get(str(name)) + return record is not None + + def rmname(self, name): + try: + self.db.delete(str(name)) + except bsddb.db.DBNotFoundError: + return False + return True + + def rmrtype(self, name, rtype): + if type(rtype) == str: + rtype = rec.rtypebyname(rtype) + rrset = self.lookup(name) + if rrset is None: + return False + for rr in rrset: + if rr.head.rtype == rtype: + rrset.remove(rr) + self.set(name, rrset) + return True + + def addrr(self, name, rr): + rrset = self.lookup(name) + if rrset is None: + rrset = [] + rrset += [rr] + self.set(name, rrset) + return True + + def listnames(self): + cursor = self.db.cursor() + ret = cursor.first() + if ret is not None: + name, record = ret + yield name + while True: + ret = cursor.next() + if ret is None: + break + name, record = ret + yield name + cursor.close() + +def rootify(rrset, origin): + for rr in rrset: + if not rr.head.name.rooted: + rr.head.name += origin + for dname, dval in rr.data.rdata.items(): + if isinstance(dval, dn.domainname) and not dval.rooted: + rr.data.rdata[dname] += origin + +class dbhandler(server.handler): + def __init__(self, dbdir, dbfile): + self.db = dnsdb(dbdir, dbfile) + self.doddns = False + self.authkeys = [] + + def handle(self, query, pkt, origin): + resp = proto.responsefor(pkt) + if pkt.opcode == proto.QUERY: + rrset = self.db.lookup(query.name) + if rrset is None and query.name in origin: + rrset = self.db.lookup(query.name - origin) + if rrset is None: + return None + rootify(rrset, origin) + resp.anlist = [rr for rr in rrset if rr.head.rtype == query.rtype or rr.head.istype("CNAME")] + return resp + if pkt.opcode == proto.UPDATE: + logger.debug("got DDNS request") + if len(pkt.qlist) != 1 or not pkt.qlist[0].istype("SOA"): + resp.rescode = proto.FORMERR + return resp + if pkt.qlist[0].name != origin: + resp.rescode = proto.NOTAUTH + return resp + + # Check prerequisites + for rr in pkt.anlist: + if rr.ttl != 0: + resp.rescode = proto.FORMERR + return resp + if rr.head.name not in origin: + resp.rescode = proto.NOTZONE + return resp + myname = rr.head.name - origin + rrset = self.db.lookup(myname) + if rr.head.rclass == rec.CLASSANY: + if rr.data is not None: + resp.rescode = proto.FORMERR + return resp + if rr.head.rtype == proto.QTANY: + if rrset is None: + resp.rescode = proto.NXDOMAIN + return resp + else: + if rrset is not None: + for rr2 in rrset: + if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype: + break + else: + resp.rescode = proto.NXRRSET + return resp + elif rr.head.rclass == rec.CLASSNONE: + if rr.data is not None: + resp.rescode = proto.FORMERR + return resp + if rr.head.rtype == proto.QTANY: + if rrset is not None: + resp.rescode = proto.YXDOMAIN + return resp + else: + if rrset is not None: + for rr2 in rrset: + if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype: + resp.rescode = proto.YXRRSET + return resp + elif rr.head.rclass == rec.CLASSIN: + if rrset is not None: + for rr2 in rrset: + if rr2.head.name == myname and rr.head.rtype == rr2.head.rtype and rr.data == rr2.data: + break + else: + resp.rescode = proto.NXRRSET + return resp + else: + resp.rescode = FORMERR + return resp + + # Check for permission + if not self.doddns: + resp.rescode = proto.REFUSED + return resp + if type(self.authkeys) == list: + if pkt.tsigctx is None: + resp.rescode = proto.REFUSED + return resp + if pkt.tsigctx.error != 0: + resp.rescode = proto.NOTAUTH + return resp + if pkt.tsigctx.key not in self.authkeys: + resp.rescode = proto.REFUSED + return resp + elif type(self.authkeys) == None: + authorized = True + + # Do precheck on updates + for rr in pkt.aulist: + if rr.head.name not in origin: + resp.rescode = proto.NOTZONE + return resp + if rr.head.rclass == rec.CLASSIN: + if rr.head.rtype == proto.QTANY or rr.data is None: + resp.rescode = proto.FORMERR + return resp + elif rr.head.rclass == rec.CLASSANY: + if rr.data is not None: + resp.rescode = proto.FORMERR + return resp + elif rr.head.rclass == rec.CLASSNONE: + if rr.head.rtype == proto.QTANY or rr.ttl != 0 or rr.data is None: + resp.rescode = proto.FORMERR + return resp + else: + resp.rescode = proto.FORMERR + return resp + + # Perform updates + for rr in pkt.aulist: + myname = rr.head.name - origin + if rr.head.rclass == rec.CLASSIN: + logger.info("adding rr (%s)", rr) + self.db.addrr(myname, rr) + elif rr.head.rclass == rec.CLASSANY: + if rr.head.rtype == proto.QTANY: + logger.info("removing rrset (%s)", rr.head.name) + self.db.rmname(myname) + else: + logger.info("removing rrset (%s, %s)", rr.head.name, rr.head.rtype) + self.db.rmrtype(myname, rr.head.rtype) + elif rr.head.rclass == rec.CLASSNONE: + logger.info("removing rr (%s)", rr) + rrset = self.db.lookup(myname) + changed = False + if rrset is not None: + for rr2 in rrset: + if rr2.head == rr.head and rr2.data == rr.data: + rrset.remove(rr2) + changed = True + self.db.set(myname, rrset) + + return resp + + resp.rescode = proto.NOTIMP + return resp