2 # Python Domain Name Server
3 # Copyright (C) 2002 Digital Lumber, Inc.
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 import sipb_xen_database
30 from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
31 ENOTCONN, ESHUTDOWN, EINTR, EISCONN, ETIMEDOUT
33 # EXAMPLE ZONE FILE DATA STRUCTURE
36 # There are no trailing dots in the internal data
37 # structure. Although it's hard to tell by reading
38 # the RFC's, the dots on the end of names are just
39 # used internally by the resolvers and servers to
40 # see if they need to append a domain name onto
41 # the end of names. There are no trailing dots
42 # on names in queries on the network.
44 examplenet = {'example.net':{'SOA':[{'class':'IN',
46 'mname':'ns1.example.net',
47 'rname':'hostmaster.example.net',
55 'nsdname':'ns1.example.net'},
57 'nsdname':'ns2.example.net'}],
61 'exchange':'mail.example.net'}]},
62 'server1.example.net':{'A':[{'class':'IN',
64 'address':'10.1.2.3'}]},
65 'www.example.net':{'CNAME':[{'class':'IN',
67 'cname':'server1.example.net'}]},
68 'router.example.net':{'A':[{'class':'IN',
70 'address':'10.1.2.1'},
73 'address':'10.2.1.1'}]}
77 # setup logging defaults
84 def file(name, mode='r', buffer=0):
85 return open(name, mode, buffer)
89 logfile.write(msg+'\n')
92 return time.strftime('%m/%d/%y %H:%M:%S')+ '-'
98 log(0,'inttoasc cannot convert ' + repr(number))
99 if hs[-1:].upper() == 'L':
103 result = chr(int(hs[-2:],16)) + result
105 result = chr(int(hs,16)) + result
109 def asctoint(ascnum):
111 for i in range(len(ascnum)-1,-1,-1):
112 rascnum = rascnum + ascnum[i]
116 x = ord(c) << (8*count)
122 def ipv6net_aton(ip_string):
124 # first account for shorthand syntax
125 pieces = ip_string.split(':')
132 ip_string = ip_string.replace('::',':'+rs)
133 if ip_string[0] == ':':
134 ip_string = ip_string[1:]
135 pieces = ip_string.split(':')
140 packed_ip = packed_ip + chr(int(part[:2],16))+ chr(int(part[2:],16))
143 def ipv6net_ntoa(packed_ip):
147 ip_string = ip_string + hex(ord(c))[2:]
150 ip_string = ip_string + ':'
152 return ip_string[:-1]
154 def getversion(qname, id, rd, ra, versionstr):
162 msg.question.qname = qname
163 msg.question.qtype = 'TXT'
164 msg.question.qclass = 'CH'
165 if qname == 'version.bind':
166 msg.header.ancount = 2
167 msg.answerlist.append({qname:{'CNAME':[{'cname':'version.oak',
170 msg.answerlist.append({'version.oak':{'TXT':[{'txtdata':versionstr,
174 msg.header.ancount = 1
175 msg.answerlist.append({qname:{'TXT':[{'txtdata':versionstr,
182 rcodestr = 'NOERROR(No error condition)'
184 rcodestr = 'FORMERR(Format Error)'
186 rcodestr = 'SERVFAIL(Internal failure)'
188 rcodestr = 'NXDOMAIN(Name does not exist)'
190 rcodestr = 'NOTIMP(Not Implemented)'
192 rcodestr = 'REFUSED(Security violation)'
194 rcodestr = 'YXDOMAIN(Name exists)'
196 rcodestr = 'YXRRSET(RR exists)'
198 rcodestr = 'NXRRSET(RR does not exist)'
200 rcodestr = 'NOTAUTH(Server not Authoritative)'
202 rcodestr = 'NOTZONE(Name not in zone)'
204 rcodestr = 'Unknown RCODE(' + str(rcode) + ')'
207 def printrdata(dnstype, rdata):
209 return rdata['address']
210 elif dnstype == 'MX':
211 return str(rdata['preference'])+'\t'+rdata['exchange']+'.'
212 elif dnstype == 'NS':
213 return rdata['nsdname']+'.'
214 elif dnstype == 'PTR':
215 return rdata['ptrdname']+'.'
216 elif dnstype == 'CNAME':
217 return rdata['cname']+'.'
218 elif dnstype == 'SOA':
219 return (rdata['mname']+'.\t'+rdata['rname']+'. (\n'+35*' '+str(rdata['serial'])+'\n'+
220 35*' '+str(rdata['refresh'])+'\n'+35*' '+str(rdata['retry'])+'\n'+35*' '+
221 str(rdata['expire'])+'\n'+35*' '+str(rdata['minimum'])+' )')
223 def makezonedatalist(zonedata, origin):
224 # unravel structure into list
227 soanode = zonedata[origin]
228 zonedatalist.append([origin+'.','SOA',soanode['SOA'][0]])
229 for item in soanode.keys():
231 for listitem in soanode[item]:
232 zonedatalist.append([origin+'.', item, listitem])
233 for nodename in zonedata.keys():
234 if nodename != origin:
235 for item in zonedata[nodename].keys():
236 for listitem in zonedata[nodename][item]:
237 zonedatalist.append([nodename+'.', item, listitem])
240 def writezonefile(zonedata, origin, file):
241 zonedatalist = makezonedatalist(zonedata, origin)
242 for rr in zonedatalist:
245 line = (owner + (35-len(owner))*' ' + str(rr[2]['ttl']) + '\t\tIN\t' +
246 dnstype + '\t' + printrdata(dnstype, rr[2]))
247 file.write(line + '\n')
249 def readzonefiles(zonedict):
250 for k in zonedict.keys():
251 filepath = zonedict[k]['filename']
253 pr = zonefileparser()
254 pr.parse(zonedict[k]['origin'],filepath)
255 zonedict[k]['zonedata'] = pr.getzdict()
256 except ZonefileError, lineno:
257 log(0,'Error reading zone file ' + filepath + ' at line ' +
261 def slowloop(tofunc='',timeout=5.0):
264 map = asyncore.socket_map
267 for fd, obj in map.items():
273 starttime = time.time()
274 r,w,e = select.select(r,w,e,timeout)
275 endtime = time.time()
276 if endtime-starttime >= timeout:
278 except select.error, err:
282 log(0,'ERROR in select')
288 log(0,'KeyError in socket map')
291 obj.handle_read_event()
293 log(0,'calling HANDLE ERROR from loop')
300 log(0,'KeyError in socket map')
303 obj.handle_read_event()
305 log(0,'calling HANDLE ERROR from loop')
309 def fastloop(tofunc='',timeout=5.0):
312 polltimeout = timeout*1000
313 map = asyncore.socket_map
316 pollobj = select.poll()
317 for fd, obj in map.items():
320 flags = select.POLLIN
322 flags = flags | select.POLLOUT
324 pollobj.register(fd, flags)
327 starttime = time.time()
328 r = pollobj.poll(polltimeout)
329 endtime = time.time()
330 if endtime-starttime >= timeout:
332 except select.error, err:
336 log(0,'ERROR in select')
340 badvals = (select.POLLPRI + select.POLLERR +
341 select.POLLHUP + select.POLLNVAL)
342 if (flags & badvals):
343 if (flags & select.POLLPRI):
345 if (flags & select.POLLERR):
347 if (flags & select.POLLHUP):
349 if (flags & select.POLLNVAL):
353 if (flags & select.POLLIN):
354 obj.handle_read_event()
355 if (flags & select.POLLOUT):
356 obj.handle_write_event()
358 log(0,'KeyError in socket map')
362 sf = StringIO.StringIO()
363 traceback.print_exc(file=sf)
364 log(0,'ERROR IN LOOP:')
370 if hasattr(select,'poll'):
375 class ZonefileError(Exception):
376 def __init__(self, linenum, errordesc=''):
377 self.linenum = linenum
378 self.errordesc = errordesc
380 return str(self.linenum) + ' (' + self.errordesc + ')'
382 class zonefileparser:
385 self.dnstypes = ['A','AAAA','CNAME','HINFO','LOC','MX',
386 'NS','PTR','RP','SOA','SRV','TXT']
388 def stripcomments(self, line):
394 def strip(self, line):
395 # strip trailing linefeeds
396 if line[-1:] == '\n':
403 def addorigin(self, origin, name):
405 return name + '.' + origin
409 def getstrings(self, s):
410 if s.find('"') == -1:
416 if i != '' and i != ' ':
420 def getlocsize(self, s):
422 size = float(s[:-1])*100
431 def getloclat(self, l,c):
440 rval = ((((deg *60) + min) * 60) + secs) * 1000
442 rval = rval + (2**31)
444 rval = (2**31) - rval
446 log(0,'ERROR: unsupported latitude/longitude direction')
449 def getgname(self, name, iter):
450 if name == '0' or name == 'O':
456 for x in range(name.count('$')):
457 i = name.find('$',start)
461 if name[i-1] == '\\':
467 j = name.find('}',i+1)
468 owb = name[i+2:j].split(',')
484 rs = hex(val)[2:].lower()
486 rs = hex(val)[2:].upper()
490 rs = (width-len(rs))*'0'+rs
491 name = name[:i]+rs+name[j+1:]
496 def getrrdata(self, origin, dnstype, dnsclass, ttl, tokens):
498 rdata['class'] = dnsclass
501 rdata['address'] = tokens[0]
502 elif dnstype == 'AAAA':
503 rdata['address'] = tokens[0]
504 elif dnstype == 'CNAME':
505 rdata['cname'] = self.addorigin(origin,tokens[0].lower())
506 elif dnstype == 'HINFO':
507 sl = self.getstrings(' '.join(tokens))
510 elif dnstype == 'LOC':
512 i = tokens.index('N')
514 i = tokens.index('S')
515 lat = self.getloclat(tokens[0:i],tokens[i])
517 j = tokens.index('E')
519 j = tokens.index('W')
520 lng = self.getloclat(tokens[i+1:j],tokens[j])
521 size = self.getlocsize('1m')
522 horiz_pre = self.getlocsize('1000m')
523 vert_pre = self.getlocsize('10m')
524 if len(tokens[j+1:]) == 2:
525 size = self.getlocsize(tokens[-1:][0])
526 elif len(tokens[j+1:]) == 3:
527 size = self.getlocsize(tokens[-2:-1][0])
528 horiz_pre = self.getlocsize(tokens[-1:][0])
529 elif len(tokens[j+1:]) == 4:
530 size = self.getlocsize(tokens[-3:-2][0])
531 horiz_pre = self.getlocsize(tokens[-2:-1][0])
532 vert_pre = self.getlocsize(tokens[-1:][0])
533 if tokens[j+1][-1:] == 'm':
534 alt = int((float(tokens[j+1][:-1])*100)+10000000)
536 size = int((float(tokens[j+1])*100)+10000000)
539 rdata['horiz_pre'] = horiz_pre
540 rdata['vert_pre'] = vert_pre
541 rdata['latitude'] = lat
542 rdata['longitude'] = lng
543 rdata['altitude'] = 0
544 elif dnstype == 'MX':
545 rdata['preference'] = int(tokens[0])
546 rdata['exchange'] = self.addorigin(origin,tokens[1].lower())
547 elif dnstype == 'NS':
548 rdata['nsdname'] = self.addorigin(origin,tokens[0].lower())
549 elif dnstype == 'PTR':
550 rdata['ptrdname'] = self.addorigin(origin,tokens[0].lower())
551 elif dnstype == 'RP':
552 rdata['mboxdname'] = self.addorigin(origin,tokens[0].lower())
553 rdata['txtdname'] = self.addorigin(origin,tokens[1].lower())
554 elif dnstype == 'SOA':
555 rdata['mname'] = self.addorigin(origin,tokens[0].lower())
556 rdata['rname'] = self.addorigin(origin,tokens[1].lower())
557 rdata['serial'] = int(tokens[2])
558 rdata['refresh'] = int(tokens[3])
559 rdata['retry'] = int(tokens[4])
560 rdata['expire'] = int(tokens[5])
561 rdata['minimum'] = int(tokens[6])
562 elif dnstype == 'SRV':
563 rdata['priority'] = int(tokens[0])
564 rdata['weight'] = int(tokens[1])
565 rdata['port'] = int(tokens[2])
566 rdata['target'] = self.addorigin(origin,tokens[3].lower())
567 elif dnstype == 'TXT':
568 rdata['txtdata'] = self.getstrings(' '.join(tokens))[0]
570 raise ZonefileError(lineno,'bad DNS type')
573 def addrec(self, owner, dnstype, rrdata):
574 if self.zonedata.has_key(owner):
575 if not self.zonedata[owner].has_key(dnstype):
576 self.zonedata[owner][dnstype] = []
578 self.zonedata[owner] = {}
579 self.zonedata[owner][dnstype] = []
580 self.zonedata[owner][dnstype].append(rrdata)
582 def parse(self, origin, f):
584 if type(f) != types.FileType:
590 log(0,'Invalid path to zonefile')
601 line = self.stripcomments(line)
602 line = self.strip(line)
605 if line.find('(') >= 0:
606 # grab lines until end paren
607 if line.find(')') == -1:
608 line2 = self.stripcomments(f.readline())
610 line2 = self.strip(line2)
612 while line2.find(')') == -1:
613 line2 = self.strip(self.stripcomments(f.readline()))
616 # now strip the parenthesis
617 line = line.replace(')','')
618 line = line.replace('(','')
619 # now line equals the entire RR entry
620 tokens = line.split()
621 if tokens[0].upper() == '$ORIGIN':
623 origin = tokens[1].lower()
625 raise ZonefileError(lineno, 'bad origin')
626 elif tokens[0].upper() == '$INCLUDE':
628 f2 = file(tokens[1].lower())
630 self.parse(tokens[2].lower(), f2)
632 self.parse(origin, f2)
635 raise ZonefileError(lineno, 'bad INCLUDE directive')
636 elif tokens[0].upper() == '$TTL':
638 lastttl = int(tokens[1])
640 raise ZonefileError(lineno, 'bad TTL directive')
641 elif tokens[0].upper() == '$GENERATE':
643 lhs = tokens[2].lower()
644 dnstype = tokens[3].upper()
645 rhs = tokens[4].lower()
646 rng = tokens[1].split('-')
650 stop = int(rng[1][:i])+1
651 step = int(rng[1][i+1:])
655 for i in range(start,stop,step):
656 grhs = self.getgname(rhs,i)
657 if dnstype in ['NS','CNAME','PTR']:
658 grhs = self.addorigin(origin,grhs)
659 rrdata = self.getrrdata(origin, dnstype, 'IN', lastttl,
661 glhs = self.addorigin(origin,self.getgname(lhs,i))
662 self.addrec(glhs,dnstype, rrdata)
664 raise ZonefileError(lineno, 'bad GENERATE directive')
667 # if line begins with blank then owner is last owner
668 if line[0] in string.whitespace:
671 owner = tokens[0].lower()
675 elif owner[-1:] != '.':
676 owner = owner + '.' + origin
678 owner = owner[:-1] # strip off trailing dot
679 # line format is either: [class] [ttl] type RDATA
680 # or [ttl] [class] type RDATA
681 # - items in brackets are optional
683 # need to figure out which token is type
684 # and backfill the missing data
687 if token.upper() in self.dnstypes:
690 # the following strips off the ttl and class if they exist
693 dnsclass = lastdnsclass
695 if tokens[0].isdigit():
697 dnsclass = lastdnsclass
700 dnsclass = tokens[0].upper()
703 if tokens[0].isdigit():
705 dnsclass = tokens[1].upper()
708 dnsclass = tokens[0].upper()
711 raise ZonefileError(lineno,'bad ttl or class')
713 # make sure all of the structure is there
714 rrdata = self.getrrdata(origin, dnstype, dnsclass,
716 self.addrec(owner, dnstype, rrdata)
719 lastdnsclass = dnsclass
721 raise ZonefileError(lineno,'unable to parse line')
727 # self.zonedb = zonedb({})
731 def getview(self, msg, address, port):
733 # 1. a list of zone keys
734 # 2. whether or not to use the resolver
735 # (i.e. answer recursive queries)
736 # 3. a list of forwarder addresses
737 return ['servers.csail.mit.edu'], 1, []
739 def allowupdate(self, msg, address, port):
740 # return 1 if updates are allowed
741 # NOTE: can only update the zones
742 # returned by the getview func
745 def outpackets(self, packetlist):
746 # modify outgoing packets
750 def __init__(self, id=1):
751 self.id = id # 16bit identifier generated by queryer
752 self.qr = 0 # one bit field specifying query(0) or response(1)
753 self.opcode = 0 # 4bit field specifying type of query
754 self.aa = 0 # authoritative answer
755 self.tc = 0 # message is not truncated
756 self.rd = 1 # recursion desired
757 self.ra = 0 # recursion available?
758 self.z = 0 # reserved for future use
759 self.rcode = 0 # response code (set in response)
760 self.qdcount = 1 # number of questions, only 1 is supported
761 self.ancount = 0 # number of rrs in the answer section
762 self.nscount = 0 # number of name server rrs in authority section
763 self.arcount = 0 # number or rrs in the additional section
767 self.qname = 'localhost'
775 def __init__(self, msgdata=''):
777 self.header = dnsheader()
779 self.header = dnsheader(id=random.randrange(1,32768))
780 self.question = dnsquestion()
785 self.qtypes = {1:'A',2:'NS',3:'MD',4:'MF',5:'CNAME',6:'SOA',
786 7:'MB',8:'MG',9:'MR',10:'NULL',11:'WKS',
787 12:'PTR',13:'HINFO',14:'MINFO',15:'MX',
788 16:'TXT',17:'RP',28:'AAAA',29:'LOC',33:'SRV',
789 38:'A6',39:'DNAME',251:'IXFR',252:'AXFR',
790 253:'MAILB',254:'MAILA',255:'ANY'}
792 for key in self.qtypes.keys():
793 self.rqtypes[self.qtypes[key]] = key
794 self.qclasses = {1:'IN',2:'CS',3:'CH',4:'HS',254:'NONE',255:'ANY'}
796 for key in self.qclasses.keys():
797 self.rqclasses[self.qclasses[key]] = key
800 self.processpkt(msgdata)
802 def getdomainname(self, data, i):
803 log(4,'IN GETDOMAINNAME')
806 labellength= ord(data[i])
807 log(4,'labellength:' + str(labellength))
809 while labellength != 0:
810 while labellength >= 192:
816 i = asctoint(chr(ord(data[i-1]) & 63)+data[i])
817 log(4,'new index:'+str(i))
818 labellength = ord(data[i])
819 log(4,'labellength:' + str(labellength))
822 domainname = domainname + '.' + data[i:i+labellength]
824 domainname = data[i:i+labellength]
825 log(4,'domainname:'+domainname)
827 labellength = ord(data[i])
828 log(4,'labellength:' + str(labellength))
833 return domainname.lower(), rindex
835 def getrrdata(self, type, msgdata, rdlength, i):
836 log(4,'unpacking RR data')
837 rdata = msgdata[i:i+rdlength]
839 return {'address':socket.inet_ntoa(rdata)}
841 return {'address':ipv6net_ntoa(rdata)}
842 elif type == 'CNAME':
843 cname, i = self.getdomainname(msgdata,i)
844 return {'cname':cname}
845 elif type == 'HINFO':
846 cpulen = ord(rdata[0])
847 cpu = rdata[1:cpulen+1]
849 'os':rdata[cpulen+2:]}
851 return {'version':ord(rdata[0]),
852 'size':self.locsize(rdata[1]),
853 'horiz_pre':self.locsize(rdata[2]),
854 'vert_pre':self.locsize(rdata[3]),
855 'latitude':asctoint(rdata[4:8]),
856 'longitude':asctoint(rdata[8:12]),
857 'altitude':asctoint(rdata[12:16])}
859 exchange, i = self.getdomainname(msgdata,i+2)
860 return {'preference':asctoint(rdata[:2]),
863 nsdname, i = self.getdomainname(msgdata,i)
864 return {'nsdname':nsdname}
866 ptrdname, i = self.getdomainname(msgdata,i)
867 return {'ptrdname':ptrdname}
869 mboxdname, i = self.getdomainname(msgdata,i)
870 txtdname, i = self.getdomainname(msgdata,i)
871 return {'mboxdname':mboxdname,
874 mname, i = self.getdomainname(msgdata,i)
875 rname, i = self.getdomainname(msgdata,i)
876 return {'mname':mname,
878 'serial':asctoint(msgdata[i:i+4]),
879 'refresh':asctoint(msgdata[i+4:i+8]),
880 'retry':asctoint(msgdata[i+8:i+12]),
881 'expire':asctoint(msgdata[i+12:i+16]),
882 'minimum':asctoint(msgdata[i+16:i+20])}
884 target, i = self.getdomainname(msgdata,i+6)
885 return {'priority':asctoint(rdata[0:2]),
886 'weight':asctoint(rdata[2:4]),
887 'port':asctoint(rdata[4:6]),
890 return {'txtdata':rdata[1:]}
892 return {'rdata':rdata}
894 def getrr(self, data, i):
895 log(4,'unpacking RR name')
896 name, i = self.getdomainname(data, i)
897 type = asctoint(data[i:i+2])
898 type = self.qtypes.get(type,chr(type))
899 klass = asctoint(data[i+2:i+4])
900 klass = self.qclasses.get(klass,chr(klass))
901 ttl = asctoint(data[i+4:i+8])
902 rdlength = asctoint(data[i+8:i+10])
903 rrdata = self.getrrdata(type,data,rdlength,i+10)
905 rrdata['class'] = klass
906 rr = {name:{type:[rrdata]}}
907 return rr, i+10+rdlength
909 def processpkt(self, msgdata):
910 self.header.id = asctoint(msgdata[:2])
911 self.header.qr = ord(msgdata[2]) >> 7
912 self.header.opcode = (ord(msgdata[2]) & 127) >> 3
913 if self.header.opcode == 5:
915 log(4,'processing UPDATE packet')
920 del self.header.qdcount
921 del self.header.ancount
922 del self.header.nscount
923 del self.header.arcount
925 self.zone = dnsupdatezone()
930 self.header.rcode = ord(msgdata[3]) & 15
931 self.header.zocount = asctoint(msgdata[4:6])
932 self.header.prcount = asctoint(msgdata[6:8])
933 self.header.upcount = asctoint(msgdata[8:10])
934 self.header.arcount = asctoint(msgdata[10:12])
940 for x in range(self.header.zocount):
941 (dn, i) = self.getdomainname(msgdata,i)
943 type = asctoint(msgdata[i:i+2])
944 self.zone.ztype = self.qtypes.get(type,chr(type))
945 klass = asctoint(msgdata[i+2:i+4])
946 self.zone.zclass = self.qclasses.get(klass,chr(klass))
948 for x in range(self.header.prcount):
949 rr, i = self.getrr(msgdata,i)
950 self.prlist.append(rr)
951 for x in range(self.header.upcount):
952 rr, i = self.getrr(msgdata,i)
953 self.uplist.append(rr)
954 for x in range(self.header.arcount):
955 rr, i = self.getrr(msgdata,i)
956 self.adlist.append(rr)
958 self.header.aa = (ord(msgdata[2]) & 4) >> 2
959 self.header.tc = (ord(msgdata[2]) & 2) >> 1
960 self.header.rd = ord(msgdata[2]) & 1
961 self.header.ra = ord(msgdata[3]) >> 7
962 self.header.z = (ord(msgdata[3]) & 112) >> 4
963 self.header.rcode = ord(msgdata[3]) & 15
964 self.header.qdcount = asctoint(msgdata[4:6])
965 self.header.ancount = asctoint(msgdata[6:8])
966 self.header.nscount = asctoint(msgdata[8:10])
967 self.header.arcount = asctoint(msgdata[10:12])
969 for x in range(self.header.qdcount):
970 log(4,'unpacking question')
971 (dn, i) = self.getdomainname(msgdata,i)
972 self.question.qname = dn
973 rrtype = asctoint(msgdata[i:i+2])
974 self.question.qtype = self.qtypes.get(rrtype,chr(rrtype))
975 klass = asctoint(msgdata[i+2:i+4])
976 self.question.qclass = self.qclasses.get(klass,chr(klass))
978 for x in range(self.header.ancount):
979 log(4,'unpacking answer RR')
980 rr, i = self.getrr(msgdata,i)
981 self.answerlist.append(rr)
982 for x in range(self.header.nscount):
983 log(4,'unpacking auth RR')
984 rr, i = self.getrr(msgdata,i)
985 self.authlist.append(rr)
986 for x in range(self.header.arcount):
987 log(4,'unpacking additional RR')
988 rr, i = self.getrr(msgdata,i)
989 self.addlist.append(rr)
993 # pad string with chr(0)'s so that
994 # return string length is l
998 def locsize(self, s):
1003 def packlocsize(self, x):
1004 return chr((x[0] << 4) + x[1])
1006 def packdomainname(self, name, i, msgcomp):
1007 log(4,'packing domainname: ' + name)
1010 if name in msgcomp.keys():
1011 log(4,'using pointer for: ' + name)
1012 return msgcomp[name]
1014 tokens = name.split('.')
1015 for j in range(len(tokens)):
1016 packedname = packedname + chr(len(tokens[j])) + tokens[j]
1017 nameleft = '.'.join(tokens[j+1:])
1018 if nameleft in msgcomp.keys():
1019 log(4,'using pointer for: ' + nameleft)
1020 return packedname+msgcomp[nameleft]
1021 # haven't used a pointer so put this in the dictionary
1022 pointer = inttoasc(i)
1023 if len(pointer) == 1:
1024 msgcomp[name] = chr(192)+pointer
1026 msgcomp[name] = chr(192|ord(pointer[0])) + pointer[1]
1027 log(4,'added pointer for ' + name + '(' + str(i) + ')')
1028 return packedname + chr(0)
1030 def packrr(self, rr, i, msgcomp):
1031 rrname = rr.keys()[0]
1032 rrtype = rr[rrname].keys()[0]
1033 if self.rqtypes.has_key(rrtype):
1034 typeval = self.rqtypes[rrtype]
1036 typeval = ord(rrtype)
1037 dbrec = rr[rrname][rrtype][0]
1039 rclass = self.rqclasses[dbrec['class']]
1040 packedrr = (self.packdomainname(rrname, i, msgcomp) +
1041 self.pds(inttoasc(typeval),2) +
1042 self.pds(inttoasc(rclass),2) +
1043 self.pds(inttoasc(ttl),4))
1044 i = i + len(packedrr) + 2
1046 rdata = socket.inet_aton(dbrec['address'])
1047 elif rrtype == 'AAAA':
1048 rdata = ipv6net_aton(dbrec['address'])
1049 elif rrtype == 'CNAME':
1050 rdata = self.packdomainname(dbrec['cname'], i, msgcomp)
1051 elif rrtype == 'HINFO':
1052 rdata = (chr(len(dbrec['cpu'])) + dbrec['cpu'] +
1053 chr(len(dbrec['os'])) + dbrec['os'])
1054 elif rrtype == 'LOC':
1055 rdata = (chr(dbrec['version']) +
1056 self.packlocsize(dbrec['size']) +
1057 self.packlocsize(dbrec['horiz_pre']) +
1058 self.packlocsize(dbrec['vert_pre']) +
1059 self.pds(inttoasc(dbrec['latitude']),4) +
1060 self.pds(inttoasc(dbrec['longitude']),4) +
1061 self.pds(inttoasc(dbrec['altitude']),4))
1062 elif rrtype == 'MX':
1063 rdata = (self.pds(inttoasc(dbrec['preference']),2) +
1064 self.packdomainname(dbrec['exchange'], i+2, msgcomp))
1065 elif rrtype == 'NS':
1066 rdata = self.packdomainname(dbrec['nsdname'], i, msgcomp)
1067 elif rrtype == 'PTR':
1068 rdata = self.packdomainname(dbrec['ptrdname'], i, msgcomp)
1069 elif rrtype == 'RP':
1070 rdata1 = self.packdomainname(dbrec['mboxdname'], i , msgcomp)
1072 rdata2 = self.packdomainname(dbrec['mboxdname'], i , msgcomp)
1073 rdata = rdata1 + rdata2
1074 elif rrtype == 'SOA':
1075 rdata1 = self.packdomainname(dbrec['mname'], i, msgcomp)
1077 rdata2 = self.packdomainname(dbrec['rname'], i, msgcomp)
1080 self.pds(inttoasc(dbrec['serial']),4) +
1081 self.pds(inttoasc(dbrec['refresh']),4) +
1082 self.pds(inttoasc(dbrec['retry']),4) +
1083 self.pds(inttoasc(dbrec['expire']),4) +
1084 self.pds(inttoasc(dbrec['minimum']),4))
1085 elif rrtype == 'SRV':
1086 rdata = (self.pds(inttoasc(dbrec['priority']),2) +
1087 self.pds(inttoasc(dbrec['weight']),2) +
1088 self.pds(inttoasc(dbrec['port']),2) +
1089 self.packdomainname(dbrec['target'], i+6, msgcomp))
1090 elif rrtype == 'TXT':
1091 rdata = chr(len(dbrec['txtdata'])) + dbrec['txtdata']
1093 rdata = dbrec['rdata']
1095 return packedrr+self.pds(inttoasc(len(rdata)),2)+rdata
1098 # keep dictionary of names packed (so we can use pointers)
1101 if self.header.id > 65535:
1102 log(0,'building packet with bad ID field')
1104 msgdata = inttoasc(self.header.id)
1105 if len(msgdata) == 1:
1106 msgdata = chr(0) + msgdata
1107 h1 = ((self.header.qr << 7) +
1108 (self.header.opcode << 3) +
1109 (self.header.aa << 2) +
1110 (self.header.tc << 1) +
1112 h2 = ((self.header.ra << 7) +
1113 (self.header.z << 4) +
1114 (self.header.rcode))
1115 msgdata = msgdata + chr(h1) + chr(h2)
1116 msgdata = msgdata + self.pds(inttoasc(self.header.qdcount),2)
1117 msgdata = msgdata + self.pds(inttoasc(self.header.ancount),2)
1118 msgdata = msgdata + self.pds(inttoasc(self.header.nscount),2)
1119 msgdata = msgdata + self.pds(inttoasc(self.header.arcount),2)
1121 msgdata = msgdata + self.packdomainname(self.question.qname, len(msgdata), msgcomp)
1122 if self.rqtypes.has_key(self.question.qtype):
1123 typeval = self.rqtypes[self.question.qtype]
1125 typeval = ord(self.question.qtype)
1126 msgdata = msgdata + self.pds(inttoasc(typeval),2)
1127 if self.rqclasses.has_key(self.question.qclass):
1128 classval = self.rqclasses[self.question.qclass]
1130 classval = ord(self.question.qclass)
1131 msgdata = msgdata + self.pds(inttoasc(classval),2)
1134 # {'name' : {'type' : [rdata, rdata, ...]}
1135 # example: {'test.blah.net': {'A': [{'address': '10.1.1.2',
1137 for rr in self.answerlist:
1138 log(4,'packing answer RR')
1139 msgdata = msgdata + self.packrr(rr, len(msgdata), msgcomp)
1140 for rr in self.authlist:
1141 log(4,'packing auth RR')
1142 msgdata = msgdata + self.packrr(rr, len(msgdata), msgcomp)
1143 for rr in self.addlist:
1144 log(4,'packing additional RR')
1145 msgdata = msgdata + self.packrr(rr, len(msgdata), msgcomp)
1150 print 'ID: ' +str(self.header.id)
1152 print 'QR: RESPONSE'
1155 if self.header.opcode == 0:
1156 print 'OPCODE: STANDARD QUERY'
1157 elif self.header.opcode == 1:
1158 print 'OPCODE: INVERSE QUERY'
1159 elif self.header.opcode == 2:
1160 print 'OPCODE: SERVER STATUS REQUEST'
1161 elif self.header.opcode == 5:
1162 print 'UPDATE REQUEST'
1164 print 'OPCODE: UNKNOWN QUERY TYPE'
1165 if self.header.opcode != 5:
1167 print 'AA: AUTHORITATIVE ANSWER'
1169 print 'AA: NON-AUTHORITATIVE ANSWER'
1171 print 'TC: MESSAGE IS TRUNCATED'
1173 print 'TC: MESSAGE IS NOT TRUNCATED'
1175 print 'RD: RECURSION DESIRED'
1177 print 'RD: RECURSION NOT DESIRED'
1179 print 'RA: RECURSION AVAILABLE'
1181 print 'RA: RECURSION IS NOT AVAILABLE'
1182 if self.header.rcode == 1:
1183 printrcode = 'FORMERR'
1184 elif self.header.rcode == 2:
1185 printrcode = 'SERVFAIL'
1186 elif self.header.rcode == 3:
1187 printrcode = 'NXDOMAIN'
1188 elif self.header.rcode == 4:
1189 printrcode = 'NOTIMP'
1190 elif self.header.rcode == 5:
1191 printrcode = 'REFUSED'
1192 elif self.header.rcode == 6:
1193 printrcode = 'YXDOMAIN'
1194 elif self.header.rcode == 7:
1195 printrcode = 'YXRRSET'
1196 elif self.header.rcode == 8:
1197 printrcode = 'NXRRSET'
1198 elif self.header.rcode == 9:
1199 printrcode = 'NOTAUTH'
1200 elif self.header.rcode == 10:
1201 printrcode = 'NOTZONE'
1203 printrcode = 'NOERROR'
1204 print 'RCODE: ' + printrcode
1205 if self.header.opcode == 5:
1206 print 'NUMBER OF RRs in the Zone Section: ' + str(self.header.zocount)
1207 print 'NUMBER OF RRs in the Prerequisite Section: ' + str(self.header.prcount)
1208 print 'NUMBER OF RRs in the Update Section: ' + str(self.header.upcount)
1209 print 'NUMBER OF RRs in the Additional Data Section: ' + str(self.header.arcount)
1210 print 'ZONE SECTION:'
1211 print 'zname: ' + self.zone.zname
1212 print 'zonetype: ' + self.zone.ztype
1213 print 'zoneclass: ' + self.zone.zclass
1214 print 'PREREQUISITE RRs:'
1215 for rr in self.prlist:
1218 for rr in self.uplist:
1220 print 'ADDITIONAL RRs:'
1221 for rr in self.addlist:
1226 print 'NUMBER OF QUESTION RRs: ' + str(self.header.qdcount)
1227 print 'NUMBER OF ANSWER RRs: ' + str(self.header.ancount)
1228 print 'NUMBER OF NAME SERVER RRs: ' + str(self.header.nscount)
1229 print 'NUMBER OF ADDITIONAL RRs: ' + str(self.header.arcount)
1230 print 'QUESTION SECTION:'
1231 print 'qname: ' + self.question.qname
1232 print 'querytype: ' + self.question.qtype
1233 print 'queryclass: ' + self.question.qclass
1235 for rr in self.answerlist:
1237 print 'AUTHORITY RRs:'
1238 for rr in self.authlist:
1240 print 'ADDITIONAL RRs:'
1241 for rr in self.addlist:
1245 def __init__(self, zdict):
1248 for k in self.zdict.keys():
1249 if self.zdict[k]['type'] == 'slave':
1250 self.zdict[k]['lastupdatetime'] = 0
1252 def error(self, id, qname, querytype, queryclass, rcode):
1254 error.header.id = id
1255 error.header.rcode = rcode
1257 error.question.qname = qname
1258 error.question.qtype = querytype
1259 error.question.qclass = queryclass
1262 def getorigin(self, zkey):
1264 if self.zdict.has_key(zkey):
1265 origin = self.zdict[zkey]['origin']
1268 def getmasterip(self, zkey):
1270 if self.zdict.has_key(zkey):
1271 if self.zdict[zkey].has_key('masterip'):
1272 masterip = self.zdict[zkey]['masterip']
1275 def zonetrans(self, query):
1276 # build a list of messages
1277 # each message contains one rr of the zone
1278 # the first and last message are the
1280 origin = query.question.qname
1281 querytype = query.question.qtype
1283 for zonekey in self.zdict.keys():
1284 if self.zdict[zonekey]['origin'] == query.question.qname:
1288 zonedata = self.zdict[zkey]['zonedata']
1289 queryid = query.header.id
1290 soarec = zonedata[origin]['SOA'][0]
1291 soa = {origin:{'SOA':[soarec]}}
1292 curserial = soarec['serial']
1294 if querytype == 'IXFR':
1295 clientserial = query.authlist[0][origin]['SOA'][0]['serial']
1296 if clientserial < curserial:
1297 for i in range(clientserial,curserial+1):
1298 if self.updates[zkey].has_key(i):
1299 for rr in self.updates[zkey][i]['added']:
1301 for rr in self.updates[zkey][i]['removed']:
1304 rrlist.insert(0,soa)
1309 for nodename in zonedata.keys():
1310 for rrtype in zonedata[nodename].keys():
1311 if not (rrtype == 'SOA' and nodename == origin):
1312 for rr in zonedata[nodename][rrtype]:
1313 rrlist.append({nodename:{rrtype:[rr]}})
1314 rrlist.insert(0,soa)
1319 msg.header.id = queryid
1323 msg.header.qdcount = 1
1324 msg.question.qname = origin
1325 msg.question.qtype = querytype
1326 msg.question.qclass = 'IN'
1327 msg.header.ancount = 1
1328 msg.answerlist.append(rr)
1332 def update_zone(self, rrlist, params):
1336 origin = soa.keys()[0]
1338 rrname = rr.keys()[0]
1339 rrtype = rr[rrname].keys()[0]
1340 dbrec = rr[rrname][rrtype][0]
1341 if zonedata.has_key(rrname):
1342 if not zonedata[rrname].has_key(rrtype):
1343 zonedata[rrname][rrtype] = []
1345 zonedata[rrname] = {}
1346 zonedata[rrname][rrtype] = []
1347 zonedata[rrname][rrtype].append(dbrec)
1348 self.zdict[zonekey]['zonedata'] = zonedata
1349 curtime = time.time()
1350 self.zdict[zonekey]['lastupdatetime'] = curtime
1352 f = file(self.zdict[zonekey]['filename'],'w')
1353 writezonefile(zonedata, self.zdict[zonekey]['origin'], f)
1356 log(0,'unable to write zone ' + zonekey + 'to disk')
1357 log(1,'finished zone transfer for: ' + zonekey + ' (' + str(curtime) + ')')
1359 def remove_zone(self, zonekey):
1360 if self.zdict.has_key(zonekey):
1361 del self.zdict[zonekey]
1363 def getslaves(self, curtime):
1365 for k in self.zdict.keys():
1366 if self.zdict[k]['type'] == 'slave':
1367 origin = self.zdict[k]['origin']
1368 refresh = self.zdict[k]['zonedata'][origin]['SOA'][0]['refresh']
1369 if self.zdict[k]['lastupdatetime'] + refresh < curtime:
1370 rlist.append((k, origin, self.zdict[k]['masterip']))
1373 def zmatch(self, qname, zkeys):
1375 if self.zdict.has_key(zkey):
1376 origin = self.zdict[zkey]['origin']
1377 if qname.rfind(origin) != -1:
1381 def getzlist(self, name, zone):
1385 i = name.rfind(zone)
1388 firstpart = name[:i-1]
1389 partlist = firstpart.split('.')
1392 for x in range(len(partlist)):
1393 lastpart = partlist[x] + '.' + lastpart
1394 zlist.append(lastpart)
1397 def lookup(self, zkeys, query, addr, server, dorecursion, flist, cbfunc):
1398 # handle zone transfers seperately
1399 qname = query.question.qname
1400 querytype = query.question.qtype
1401 queryclass = query.question.qclass
1402 if querytype in ['AXFR','IXFR']:
1403 for zkey in self.zdict.keys():
1405 if qname == self.zdict[zkey]['origin']:
1406 answerlist = self.zonetrans(query)
1410 cbfunc(query, addr, server, dorecursion, flist, answerlist)
1412 zonekey = self.zmatch(qname, zkeys)
1414 origin = self.zdict[zonekey]['origin']
1415 zonedict = self.zdict[zonekey]['zonedata']
1421 answer.header.aa = 1
1422 answer.header.id = query.header.id
1423 answer.header.qr = 1
1424 answer.header.opcode = query.header.opcode
1425 answer.header.rcode = 4
1426 answer.header.ra = dorecursion
1427 answer.question.qname = query.question.qname
1428 answer.question.qtype = query.question.qtype
1429 answer.question.qclass = query.question.qclass
1430 answer.header.ra = dorecursion
1431 s = '.servers.csail.mit.edu'
1432 if qname.endswith(s):
1433 host = qname[:-len(s)]
1434 value = sipb_xen_database.NIC.get_by(hostname=host)
1439 rranswerlist.append({qname: {'A': [{'address': ip,
1442 if zonedict.has_key(qname):
1443 # found the node, now take care of CNAMEs
1444 if zonedict[qname].has_key('CNAME'):
1445 if querytype != 'CNAME':
1447 while nodetype == 'CNAME':
1448 rranswerlist.append({qname:{'CNAME':[zonedict[qname]['CNAME'][0]]}})
1449 qname = zonedict[qname]['CNAME'][0]['cname']
1450 if zonedict.has_key(qname):
1451 nodetype = zonedict[qname].keys()[0]
1453 # error, shouldn't have a CNAME that points to nothing
1455 # if we get this far, then the record has matched and we should return
1456 # a reply that has no error (even if there is no info macthing the qtype)
1457 answer.header.rcode = 0
1458 answernode = zonedict[qname]
1459 if querytype == 'ANY':
1460 for type in answernode.keys():
1461 for rec in answernode[type]:
1462 rranswerlist.append({qname:{type:[rec]}})
1463 elif answernode.has_key(querytype):
1464 for rec in answernode[querytype]:
1465 rranswerlist.append({qname:{querytype:[rec]}})
1466 # do rrset ordering (cyclic)
1467 if len(answernode[querytype]) > 1:
1468 rec = answernode[querytype].pop(0)
1469 answernode[querytype].append(rec)
1471 # remove all cname rrs from answerlist
1474 # would check for wildcards here (but aren't because they seem bad)
1475 # see if we need to give a referral
1476 zlist = self.getzlist(qname,origin)
1477 for zonename in zlist:
1478 if zonedict.has_key(zonename):
1479 if zonedict[zonename].has_key('NS'):
1480 answer.header.rcode = 0
1482 for rec in zonedict[zonename]['NS']:
1483 rrnslist.append({zonename:{'NS':[rec]}})
1484 nsdname = rec['nsdname']
1485 # add glue records if they exist
1486 if zonedict.has_key(nsdname):
1487 if zonedict[nsdname].has_key('A'):
1488 for gluerec in zonedict[nsdname]['A']:
1489 rraddlist.append({nsdname:{'A':[gluerec]}})
1490 # negative caching stuff
1492 if not rranswerlist:
1493 # NOTE: RFC1034 section 4.3.4 says we should add the SOA record
1494 # to the additional section of the response. BIND adds
1495 # it to the ns section though
1496 answer.header.rcode = 3
1497 rrnslist.append({origin:{'SOA':[zonedict[origin]['SOA'][0]]}})
1499 for rec in zonedict[origin]['NS']:
1500 rrnslist.append({origin:{'NS':[rec]}})
1501 answer.header.ancount = len(rranswerlist)
1502 answer.header.nscount = len(rrnslist)
1503 answer.header.arcount = len(rraddlist)
1504 answer.answerlist = rranswerlist
1505 answer.authlist = rrnslist
1506 answer.addlist = rraddlist
1507 cbfunc(query, addr, server, dorecursion, flist, [answer])
1509 cbfunc(query, addr, server, dorecursion, flist, [])
1511 def handle_update(self, msg, addr, ns):
1514 for zonekey in self.zdict.keys():
1515 if (self.zdict[zonekey]['type'] == 'master' and
1516 self.zdict[zonekey]['origin'] == msg.zone.zname):
1519 log(2,'SENDING NOTAUTH UPDATE ERROR')
1520 errormsg = self.error(msg.header.id, msg.zone.zname,
1521 msg.zone.ztype, msg.zone.zclass, 9)
1522 return errormsg, '', slaves
1523 # find the slaves for the zone
1524 if self.zdict[zkey].has_key('slaves'):
1525 slaves = self.zdict[zkey]['slaves']
1526 origin = self.zdict[zkey]['origin']
1527 zd = self.zdict[zkey]['zonedata']
1528 # check the permissions
1529 if not ns.config.allowupdate(msg, addr[0], addr[1]):
1530 log(2,'SENDING REFUSED UPDATE ERROR')
1531 errormsg = self.error(msg.header.id, msg.zone.zname,
1532 msg.zone.ztype, msg.zone.zclass, 5)
1533 return errormsg, origin, slaves
1534 # now check the prereqs
1536 for rr in msg.prlist:
1537 rrname = rr.keys()[0]
1538 rrtype = rr[rrname].keys()[0]
1539 dbrec = rr[rrname][rrtype][0]
1540 if dbrec['ttl'] != 0:
1541 log(2,'FORMERROR(1)')
1542 errormsg = self.error(msg.header.id, msg.zone.zname,
1543 msg.zone.ztype, msg.zone.zclass, 1)
1544 return errormsg, origin, slaves
1545 if rrname.rfind(msg.zone.zname) == -1:
1546 log(2,'NOTZONE(10)')
1547 errormsg = self.error(msg.header.id, msg.zone.zname,
1548 msg.zone.ztype, msg.zone.zclass, 10)
1549 return errormsg, origin, slaves
1550 if dbrec['class'] == 'ANY':
1552 log(2,'FORMERROR(1)')
1553 errormsg = self.error(msg.header.id, msg.zone.zname,
1554 msg.zone.ztype, msg.zone.zclass, 1)
1555 return errormsg, origin, slaves
1557 if not zd.has_key(rrname):
1558 log(2,'NXDOMAIN(3)')
1559 errormsg = self.error(msg.header.id, msg.zone.zname,
1560 msg.zone.ztype, msg.zone.zclass, 3)
1561 return errormsg, origin, slaves
1564 if zd.has_key(rrname):
1565 if zd[rrname].has_key(rrtype):
1569 errormsg = self.error(msg.header.id, msg.zone.zname,
1570 msg.zone.ztype, msg.zone.zclass, 8)
1571 return errormsg, origin, slaves
1572 if dbrec['class'] == 'NONE':
1574 log(2,'FORMERROR(1)')
1575 errormsg = self.error(msg.header.id, msg.zone.zname,
1576 msg.zone.ztype, msg.zone.zclass, 1)
1577 return errormsg, origin, slaves
1579 if zd.has_key(rrname):
1580 log(2,'YXDOMAIN(6)')
1581 errormsg = self.error(msg.header.id, msg.zone.zname,
1582 msg.zone.ztype, msg.zone.zclass, 6)
1583 return errormsg, origin, slaves
1585 if zd.has_key(rrname):
1586 if zd[rrname].has_key(rrtype):
1588 errormsg = self.error(msg.header.id, msg.zone.zname,
1589 msg.zone.ztype, msg.zone.zclass, 7)
1590 return errormsg, origin, slaves
1591 if dbrec['class'] == msg.zone.zclass:
1592 if temprrset.has_key(rrname):
1593 if not temprrset[rrname].has_key(rrtype):
1594 temprrset[rrname][rrtype] = []
1596 temprrset[rrname] = {}
1597 temprrset[rrname][rrtype] = []
1598 temprrset[rrname][rrtype].append(dbrec)
1600 log(2,'FORMERROR(1)')
1601 errormsg = self.error(msg.header.id, msg.zone.zname,
1602 msg.zone.ztype, msg.zone.zclass, 1)
1603 return errormsg, origin, slaves
1604 for nodename in temprrset.keys():
1605 if not self.rrmatch(temprrset[nodename],zd[nodename]):
1607 errormsg = self.error(msg.header.id, msg.zone.zname,
1608 msg.zone.ztype, msg.zone.zclass, 8)
1609 return errormsg, origin, slaves
1611 # update section prescan
1612 for rr in msg.uplist:
1613 rrname = rr.keys()[0]
1614 rrtype = rr[rrname].keys()[0]
1615 dbrec = rr[rrname][rrtype][0]
1616 if rrname.rfind(msg.zone.zname) == -1:
1617 log(2,'NOTZONE(10)')
1618 errormsg = self.error(msg.header.id, msg.zone.zname,
1619 msg.zone.ztype, msg.zone.zclass, 10)
1620 return errormsg, origin, slaves
1621 if dbrec['class'] == msg.zone.zclass:
1622 if rrtype in ['ANY','MAILA','MAILB','AXFR']:
1623 log(2,'FORMERROR(1)')
1624 errormsg = self.error(msg.header.id, msg.zone.zname,
1625 msg.zone.ztype, msg.zone.zclass, 1)
1626 return errormsg, origin, slaves
1627 elif dbrec['class'] == 'ANY':
1628 if dbrec['ttl'] != 0 or dbrec['rdata'] or rrtype in ['MAILA','MAILB','AXFR']:
1629 log(2,'FORMERROR(1)')
1630 errormsg = self.error(msg.header.id, msg.zone.zname,
1631 msg.zone.ztype, msg.zone.zclass, 1)
1632 return errormsg, origin, slaves
1633 elif dbrec['class'] == 'NONE':
1634 if dbrec['ttl'] != 0 or rrtype in ['ANY','MAILA','MAILB','AXFR']:
1635 log(2,'FORMERROR(1)')
1636 errormsg = self.error(msg.header.id, msg.zone.zname,
1637 msg.zone.ztype, msg.zone.zclass, 1)
1638 return errormsg, origin, slaves
1640 log(2,'FORMERROR(1)')
1641 errormsg = self.error(msg.header.id, msg.zone.zname,
1642 msg.zone.ztype, msg.zone.zclass, 1)
1643 return errormsg, origin, slaves
1645 # now handle actual update
1646 curserial = zd[msg.zone.zname]['SOA'][0]['serial']
1647 # update the soa serial here
1649 if len(msg.uplist) > 0:
1650 # initialize history structure
1651 if not self.updates.has_key(zkey):
1652 self.updates[zkey] = {}
1653 self.updates[zkey][curserial] = {'removed':[],
1655 if curserial == 2**32:
1659 newserial = curserial + 1
1660 self.updates[zkey][newserial] = {'removed':[],
1662 zd[msg.zone.zname]['SOA'][0]['serial'] = newserial
1663 for rr in msg.uplist:
1664 rrname = rr.keys()[0]
1665 rrtype = rr[rrname].keys()[0]
1666 dbrec = rr[rrname][rrtype][0]
1667 if dbrec['class'] == msg.zone.zclass:
1669 if zd.has_key(rrname):
1670 if zd[rrname].has_key('SOA'):
1671 if dbrec['serial'] > zd[rrname]['SOA'][0]['serial']:
1672 del zd[rrname]['SOA'][0]
1673 zd[rrname]['SOA'].append(dbrec)
1675 elif rrtype == 'WKS':
1676 if zd.has_key(rrname):
1677 if zd[rrname].has_key('WKS'):
1678 rdata = zd[rrname]['WKS'][0]
1679 oldrr = {rrname:{'WKS':[rdata]}}
1680 self.updates[zkey][curserial]['removed'].append(oldrr)
1681 del zd[rrname]['WKS'][0]
1682 zd[rrname]['WKS'].append(dbrec)
1683 newrr = {rrname:{'WKS':[dbrec]}}
1684 self.updates[zkey][newserial]['added'].append(newrr)
1686 if zd.has_key(rrname):
1687 if not zd[rrname].has_key(rrtype):
1688 zd[rrname][rrtype] = []
1691 zd[rrname][rrtype] = []
1692 zd[rrname][rrtype].append(dbrec)
1693 newrr = {rrname:{rrtype:[dbrec]}}
1694 self.updates[zkey][newserial]['added'].append(newrr)
1695 elif dbrec['class'] == 'ANY':
1697 if rrname == msg.zone.zname:
1698 if zd.has_key(rrname):
1699 for dnstype in zd[rrname].keys():
1700 if dnstype not in ['SOA','NS']:
1701 for rdata in zd[rrname][dnstype]:
1702 oldrr = {rrname:{dnstype:[rdata]}}
1703 self.updates[zkey][curserial]['removed'].append(oldrr)
1704 del zd[rrname][dnstype]
1706 if zd.has_key(rrname):
1707 for dnstype in zd[rrname].keys():
1708 for rdata in zd[rrname][dnstype]:
1709 oldrr = {rrname:{dnstype:[rdata]}}
1710 self.updates[zkey][curserial]['removed'].append(oldrr)
1713 if zd.has_key(rrname):
1714 if zd[rrname].has_key(rrtype):
1715 if rrname == msg.zone.zname:
1716 if rrtype not in ['SOA','NS']:
1717 for rdata in zd[rrname][dnstype]:
1718 oldrr = {rrname:{dnstype:[rdata]}}
1719 self.updates[zkey][curserial]['removed'].append(oldrr)
1720 del zd[rrname][rrtype]
1722 for rdata in zd[rrname][dnstype]:
1723 oldrr = {rrname:{dnstype:[rdata]}}
1724 self.updates[zkey][curserial]['removed'].append(oldrr)
1725 del zd[rrname][rrtype]
1726 elif dbrec['class'] == 'NONE':
1727 if not (rrname == msg.zone.zname and rrtype in ['SOA','NS']):
1728 if zd.had_key(rrname):
1729 if zd[rrname].has_key(rrtype):
1730 for i in range(len(zd[rrname][rrtype])):
1731 if dbrec == zd[rrname][rrtype][i]:
1732 rdata = zd[rrname][dnstype][i]
1733 oldrr = {rrname:{dnstype:[rdata]}}
1734 self.updates[zkey][curserial]['removed'].append(oldrr)
1735 del zd[rrname][rrtype][i]
1736 if len(zd[rrname][rrtype]) == 0:
1737 del zd[rrname][rrtype]
1739 self.updates[zkey] = {}
1740 log(2,'SENDING UPDATE NOERROR MSG')
1741 noerrormsg = self.error(msg.header.id, msg.zone.zname,
1742 msg.zone.ztype, msg.zone.zclass, 0)
1743 return noerrormsg, origin, slaves
1746 def __init__(self,cachezone):
1747 self.cachedb = cachezone
1748 # go through and set all of the root ttls to zero
1749 for node in self.cachedb.keys():
1750 for rtype in self.cachedb[node].keys():
1751 for rr in self.cachedb[node][rtype]:
1755 # add special entries for localhost
1756 self.cachedb['localhost'] = {'A':[{'address':'127.0.0.1', 'ttl':0, 'class':'IN'}]}
1757 self.cachedb['1.0.0.127.in-addr.arpa'] = {'PTR':[{'ptrdname':'localhost', 'ttl':0,'class':'IN'}]}
1758 self.cachedb['']['SOA'] = []
1759 self.cachedb['']['SOA'].append({'class':'IN','ttl':0,'mname':'cachedb',
1760 'rname':'cachedb@localhost','serial':1,'refresh':10800,
1761 'retry':3600,'expire':604800,'minimum':3600})
1763 def hasrdata(self, irrdata, rrdatalist):
1764 # compare everything but ttls
1766 testrrdata = irrdata.copy()
1767 del testrrdata['ttl']
1768 for rrdata in rrdatalist:
1769 temprrdata = rrdata.copy()
1770 del temprrdata['ttl']
1771 if temprrdata == testrrdata:
1775 def add(self, rr, qzone, nsdname):
1776 # NOTE: can't cache records from sites
1777 # that don't own those records (i.e. example.com
1778 # can't give us A records for www.example.net)
1780 if (qzone != '') and (name[-len(qzone):] != qzone):
1781 log(2,'cache GOT possible POISON: ' + name + ' for zone ' + qzone)
1783 rtype = rr[name].keys()[0]
1784 rdata = rr[name][rtype][0]
1785 if rdata['ttl'] < 3600:
1786 log(2,'low ttl: ' + str(rdata['ttl']))
1788 rdata['ttl'] = int(time.time() + rdata['ttl'])
1792 rtype = rtype.upper()
1793 if self.cachedb.has_key(name):
1794 if self.cachedb[name].has_key(rtype):
1795 if not self.hasrdata(rdata, self.cachedb[name][rtype]):
1796 self.cachedb[name][rtype].append(rdata)
1797 log(3,'appended rdata to ' +
1798 name + '(' + rtype + ') in cache')
1800 log(3,'same rdata for ' + name + '(' +
1801 rtype + ') is already in cache')
1803 self.cachedb[name][rtype] = [rdata]
1804 log(3,'appended ' + rtype + ' and rdata to node ' +
1807 self.cachedb[name] = {rtype:[rdata]}
1808 log(3,'added node ' + name + '(' + rtype + ') to cache')
1811 def addneg(self, qname, querytype, queryclass):
1812 if not self.cachedb.has_key(qname):
1813 self.cachedb['qname'] = {querytype: [{'ttl':time.time()+3600}]}
1815 if not self.cachedb[qname].has_key(querytype):
1816 self.cachedb[qname][querytype] = [{'ttl':time.time()+3600}]
1818 def haskey(self, qname, querytype, msg=''):
1819 log(3,'looking for ' + qname + '(' + querytype + ') in cache')
1820 if self.cachedb.has_key(qname):
1824 if self.cachedb[qname].has_key('CNAME'):
1825 if querytype != 'CNAME':
1827 while nodetype == 'CNAME':
1828 if len(self.cachedb[qname]['CNAME'][0].keys()) > 1:
1829 log(3,'Adding CNAME to cache answer')
1830 rranswerlist.append({qname:{'CNAME':[self.cachedb[qname]['CNAME'][0]]}})
1831 qname = self.cachedb[qname]['CNAME'][0]['cname']
1832 if self.cachedb.has_key(qname):
1833 nodetype = self.cachedb[qname].keys()[0]
1835 # shouldn't have a CNAME that points to nothing
1837 if querytype == 'ANY':
1838 for type in self.cache[qname].keys():
1839 for rec in self.cachedb[qname][type]:
1840 # can't append negative entries
1841 if len(rec.keys()) > 1:
1842 rranswerlist.append({qname:{type:[rec]}})
1843 elif self.cachedb[qname].has_key(querytype):
1844 for rec in self.cachedb[qname][querytype]:
1845 if len(rec.keys()) > 1:
1846 rranswerlist.append({qname:{querytype:[rec]}})
1850 answer.header.id = msg.header.id
1851 answer.header.qr = 1
1852 answer.header.opcode = msg.header.opcode
1853 answer.header.ra = 1
1854 answer.question.qname = msg.question.qname
1855 answer.question.qtype = msg.question.qtype
1856 answer.question.qclass = msg.question.qclass
1857 answer.header.rcode = 0
1858 answer.header.ancount = len(rranswerlist)
1859 answer.answerlist = rranswerlist
1864 log(3,'Cache has no node for ' + qname)
1866 def getnslist(self, qname):
1867 # find the best nameserver to ask from the cache
1868 tokens = qname.split('.')
1870 curtime = time.time()
1871 for i in range(len(tokens)):
1872 domainname = '.'.join(tokens[i:])
1873 if self.cachedb.has_key(domainname):
1874 if self.cachedb[domainname].has_key('NS'):
1875 for nsrec in self.cachedb[domainname]['NS']:
1877 if nsrec.has_key('badtill'):
1878 if nsrec['badtill'] < curtime:
1879 del nsrec['badtill']
1883 log(2,'BAD SERVER, not using ' + nsrec['nsdname'])
1884 if self.cachedb.has_key(nsrec['nsdname']) and not badserver:
1885 if self.cachedb[nsrec['nsdname']].has_key('A'):
1886 for arec in self.cachedb[nsrec['nsdname']]['A']:
1887 nsdict[nsrec['rtt']] = {'name':nsrec['nsdname'],
1888 'ip':arec['address']}
1893 # nothing in the cache matches so give back the root servers
1894 for nsrec in self.cachedb['']['NS']:
1896 if nsrec.has_key('badtill'):
1897 if curtime > nsrec['badtill']:
1898 del nsrec['badtill']
1902 for arec in self.cachedb[nsrec['nsdname']]['A']:
1903 nsdict[(nsrec['rtt'])] = {'name':nsrec['nsdname'],'ip':arec['address']}
1905 return (domainname, nsdict)
1907 def badns(self, zonename, nsdname):
1908 if self.cachedb.has_key(zonename):
1909 if self.cachedb[zonename].has_key('NS'):
1910 for nsrec in self.cachedb[zonename]['NS']:
1911 if nsrec['nsdname'] == nsdname:
1912 log(2,'Setting ' + nsdname + ' as bad nameserver')
1913 nsrec['badtill'] = time.time() + 3600
1916 def updatertt(self, qname, zone, rtt):
1917 if self.cachedb.has_key(zone):
1918 if self.cachedb[zone].has_key('NS'):
1919 for rr in self.cachedb[zone]['NS']:
1920 if rr['nsdname'] == qname:
1921 log(2,'updating rtt for ' + qname + ' to ' + str(rtt))
1925 # expire all old records
1927 for nodename in self.cachedb.keys():
1928 for rrtype in self.cachedb[nodename].keys():
1929 for rdata in self.cachedb[nodename][rrtype]:
1933 self.cachedb[nodename][rrtype].remove(rdata)
1934 if len(self.cachedb[nodename][rrtype]) == 0:
1935 del self.cachedb[nodename][rrtype]
1936 if len(self.cachedb[nodename]) == 0:
1937 del self.cachedb[nodename]
1941 def zonetrans(self, queryid):
1942 # build a list of messages
1943 # each message contains one rr of the zone
1944 # the first and last message are the
1946 zonedata = self.cachedb
1948 soa = {'':{'SOA':[zonedata['']['SOA'][0]]}}
1949 for nodename in zonedata.keys():
1950 for rrtype in zonedata[nodename].keys():
1951 if not (rrtype == 'SOA' and nodename == ''):
1952 for rr in zonedata[nodename][rrtype]:
1953 rrlist.append({nodename:{rrtype:[rr]}})
1954 rrlist.insert(0,soa)
1959 msg.header.id = queryid
1963 msg.header.qdcount = 1
1964 msg.question.qname = 'cache'
1965 msg.question.qtype = 'AXFR'
1966 msg.question.qclass = 'IN'
1967 msg.header.ancount = 1
1968 msg.answerlist.append(rr)
1972 class gethostaddr(asyncore.dispatcher):
1973 def __init__(self, hostname, cbfunc, serveraddr='127.0.0.1'):
1974 asyncore.dispatcher.__init__(self)
1975 self.msg = message()
1976 self.msg.question.qname = hostname
1977 self.msg.question.qtype = 'A'
1978 self.cbfunc = cbfunc
1979 self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
1980 self.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 200 * 1024)
1981 self.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200 * 1024)
1982 self.socket.sendto(self.msg.buildpkt(), (serveraddr,53))
1984 def handle_read(self):
1985 replydata, addr = self.socket.recvfrom(1500)
1988 replymsg = message(replydata)
1990 log(0,'unable to process packet')
1992 answername = replymsg.question.qname
1994 # go through twice to catch cnames after A recs
1995 for rr in replymsg.answerlist:
1996 rrname = rr.keys()[0]
1997 rrtype = rr[rrname].keys()[0]
1998 dbrec = rr[rrname][rrtype][0]
1999 if rrname == answername and rrtype == 'CNAME':
2000 answername = dbrec['cname']
2002 for rr in replymsg.answerlist:
2003 rrname = rr.keys()[0]
2004 rrtype = rr[rrname].keys()[0]
2005 dbrec = rr[rrname][rrtype][0]
2006 if rrname == answername and rrtype == 'A':
2007 self.cbfunc(dbrec['address'])
2009 # if we got a cname and no A send query for cname
2011 self.msg = message()
2012 self.msg.question.qname = cname
2013 self.msg.question.qtype = 'A'
2014 self.socket.sendto(self.msg.buildpkt(), (serveraddr,53))
2021 def handle_write(self):
2024 def handle_connect(self):
2027 def handle_close(self):
2030 def log_info (self, message, type='info'):
2031 if __debug__ or type != 'info':
2032 log(0,'%s: %s' % (type, message))
2034 class simpleudprequest(asyncore.dispatcher):
2035 def __init__(self, msg, cbfunc, serveraddr='127.0.0.1', outqkey=''):
2036 asyncore.dispatcher.__init__(self)
2039 self.cbfunc = cbfunc
2040 self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
2041 self.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 200 * 1024)
2042 self.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200 * 1024)
2043 self.outqkey = outqkey
2044 self.socket.sendto(self.msg.buildpkt(), (serveraddr,53))
2046 def handle_read(self):
2047 replydata, addr = self.socket.recvfrom(1500)
2050 replymsg = message(replydata)
2052 log(0,'unable to process packet')
2054 self.cbfunc(replymsg, self.outqkey)
2059 def handle_write(self):
2062 def handle_connect(self):
2065 def handle_close(self):
2068 def log_info (self, message, type='info'):
2069 if __debug__ or type != 'info':
2070 log(0,'%s: %s' % (type, message))
2072 class simpletcprequest(asyncore.dispatcher):
2073 def __init__(self, msg, cbfunc, cbparams=[], serveraddr='127.0.0.1', errorfunc=''):
2074 asyncore.dispatcher.__init__(self)
2075 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
2077 self.cbfunc = cbfunc
2078 self.cbparams = cbparams
2079 self.errorfunc = errorfunc
2080 msgdata = msg.buildpkt()
2081 ml = inttoasc(len(msgdata))
2084 self.buffer = ml+msgdata
2088 log(2,'sending tcp request to ' + serveraddr)
2089 self.connect((serveraddr,53))
2091 def recv (self, buffer_size):
2093 data = self.socket.recv (buffer_size)
2095 # a closed connection is indicated by signaling
2096 # a read condition, and having recv() return 0.
2101 except socket.error, why:
2102 # winsock sometimes throws ENOTCONN
2103 if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN, ETIMEDOUT]:
2107 raise socket.error, why
2109 def handle_connect(self):
2112 def handle_msg(self, msg):
2113 if self.query.question.qtype == 'AXFR':
2114 if len(self.rrlist) == 0:
2115 if len(msg.answerlist) == 0:
2117 self.errorfunc(self.cbparams[0])
2120 rr = msg.answerlist[0]
2121 rrname = rr.keys()[0]
2122 rrtype = rr[rrname].keys()[0]
2123 self.rrlist.append(rr)
2124 if rrtype == 'SOA' and len(self.rrlist) > 1:
2127 self.cbfunc(self.rrlist, self.cbparams)
2129 self.cbfunc(self.rrlist)
2133 self.cbfunc(msg, self.cbparams)
2137 def handle_read(self):
2138 data = self.recv(8192)
2139 if len(self.rbuffer) == 0:
2140 self.rmsglength = asctoint(data[:2])
2142 self.rbuffer = self.rbuffer + data
2143 while len(self.rbuffer) >= self.rmsglength and self.rmsglength != 0:
2144 msgdata = self.rbuffer[:self.rmsglength]
2145 self.rbuffer = self.rbuffer[self.rmsglength:]
2146 if len(self.rbuffer) == 0:
2149 self.rmsglength = asctoint(self.rbuffer[:2])
2150 self.rbuffer = self.rbuffer[2:]
2152 self.handle_msg(message(msgdata))
2157 return (len(self.buffer) > 0)
2159 def handle_write(self):
2160 sent = self.send(self.buffer)
2161 self.buffer = self.buffer[sent:]
2163 def handle_close(self):
2165 self.errorfunc(self.query.question.qname)
2168 def log_info (self, message, type='info'):
2169 if __debug__ or type != 'info':
2170 log(0,'%s: %s' % (type, message))
2172 class udpdnsserver(asyncore.dispatcher):
2173 def __init__(self, port, dnsserver):
2174 asyncore.dispatcher.__init__(self)
2175 self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
2176 self.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 200 * 1024)
2177 self.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200 * 1024)
2178 self.bind(('',port))
2179 self.dnsserver = dnsserver
2180 self.maxmsgsize = 500
2182 def handle_read(self):
2185 msgdata, addr = self.socket.recvfrom(1500)
2186 self.dnsserver.handle_packet(msgdata, addr, self)
2187 except socket.error, why:
2188 if why[0] != asyncore.EWOULDBLOCK:
2189 raise socket.error, why
2191 def sendpackets(self, msglist, addr):
2193 msgdata = msg.buildpkt()
2194 if len(msgdata) > self.maxmsgsize:
2196 # take off all the answers to ensure
2197 # the packet size is small enough
2198 msg.header.ancount = 0
2199 msg.header.nscount = 0
2200 msg.header.arcount = 0
2204 msgdata = msg.buildpkt()
2205 self.sendto(msgdata, addr)
2210 def handle_write(self):
2213 def handle_connect(self):
2216 def handle_close(self):
2217 # print '1:In handle close'
2220 def log_info (self, message, type='info'):
2221 if __debug__ or type != 'info':
2222 log(0,'%s: %s' % (type, message))
2224 class tcpdnschannel(asynchat.async_chat):
2225 def __init__(self, server, s, addr):
2226 asynchat.async_chat.__init__(self, s)
2227 self.server = server
2229 self.set_terminator(None)
2230 self.databuffer = ''
2232 log(3,'Created new tcp channel')
2234 def collect_incoming_data(self, data):
2235 if self.msglength == 0:
2236 self.msglength = asctoint(data[:2])
2238 self.databuffer = self.databuffer + data
2239 if len(self.databuffer) == self.msglength:
2240 # got entire message
2241 self.server.dnsserver.handle_packet(self.databuffer, self.addr, self)
2242 self.databuffer = ''
2244 def sendpackets(self, msglist, addr):
2247 ml = inttoasc(len(x))
2253 def log_info (self, message, type='info'):
2254 if __debug__ or type != 'info':
2255 log(0,'%s: %s' % (type, message))
2257 class tcpdnsserver(asyncore.dispatcher):
2258 def __init__(self, port, dnsserver):
2259 asyncore.dispatcher.__init__(self)
2260 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
2261 self.set_reuse_addr()
2262 self.bind(('',port))
2264 self.dnsserver = dnsserver
2266 def handle_accept(self):
2267 conn, addr = self.accept()
2268 tcpdnschannel(self, conn, addr)
2270 def handle_close(self):
2273 def log_info (self, message, type='info'):
2274 if __debug__ or type != 'info':
2275 log(0,'%s: %s' % (type, message))
2278 def __init__(self, resolver, localconfig):
2279 self.resolver = resolver
2280 self.config = localconfig
2281 self.zdb = self.config.zonedatabase
2282 self.last_reap_time = time.time()
2284 self.slavesupdating = []
2286 self.sentnotify = []
2287 self.notify_retry_time = 30
2288 self.notify_retries = 4
2290 self.soatimeout = 10
2292 def error(self, id, qname, querytype, queryclass, rcode):
2294 error.header.id = id
2295 error.header.rcode = rcode
2297 error.question.qname = qname
2298 error.question.qtype = querytype
2299 error.question.qclass = queryclass
2302 def need_zonetransfer(self, zkey, origin, masterip, trynum=0):
2303 self.askedsoa[zkey] = {'masterip':masterip,
2304 'senttime':time.time(),
2308 query.header.id = random.randrange(1,32768)
2310 query.question.qname = origin
2311 query.question.qtype = 'SOA'
2312 query.question.qclass = 'IN'
2313 log(3,'slave checking for new data in ' + origin)
2314 simpleudprequest(query, self.handle_soaquery,
2317 def handle_soaquery(self, msg, zkey):
2318 origin = msg.question.qname
2319 masterip = self.askedsoa[zkey]['masterip']
2320 del self.askedsoa[zkey]
2321 if zkey not in self.slavesupdating:
2322 self.slavesupdating.append(zkey)
2324 query.header.id = random.randrange(1,32768)
2326 query.question.qname = origin
2327 query.question.qtype = 'AXFR'
2328 query.question.qclass = 'IN'
2329 log(3,'Updating slave zone: ' + zkey)
2330 simpletcprequest(query, self.handle_zonetrans,
2331 [zkey],masterip,self.handle_zterror)
2333 def handle_zonetrans(self, rrlist, params):
2334 log(1,'handling zone transfer')
2336 self.zdb.update_zone(rrlist, params)
2337 self.slavesupdating.remove(zonekey)
2339 def handle_zterror(self, zonekey):
2340 self.slavesupdating.remove(zonekey)
2341 self.zdb.remove_zone(zonekey)
2343 def rrmatch(self, rrset1, rrset2):
2344 for rrtype in rrset1.keys():
2345 if rrtype not in rrset2.keys():
2348 if len(rrset1[rrtype]) != len(rrset2[rrtype]):
2352 def process_notify(self, msg, ipaddr, port):
2353 (zkeys, dorecursion, flist) = self.config.getview(msg, ipaddr, port)
2356 origin = self.zdb.getorigin(zkey)
2357 if origin == msg.question.qname:
2358 masterip = self.zdb.getmasterip(zkey)
2362 log(3,'got NOTIFY from ' + masterip)
2363 self.need_zonetransfer(goodzkey, origin, masterip, 0)
2367 curtime = time.time()
2368 for origin, ipaddr, trynum, senttime in self.sentnotify:
2369 if senttime + self.notify_retry_time > curtime:
2370 self.notifys.append((origin, ipaddr, trynum))
2371 self.sentnotify.remove((origin, ipaddr, trynum, senttime))
2372 for origin, ipaddr, trynum in self.notifys:
2374 msg.question.qname = origin
2375 msg.question.qtype = 'SOA'
2376 msg.question.qclass = 'IN'
2377 msg.header.opcode = 4
2378 # there probably is a better way to do this
2380 self.resolver.send_to([msg],(ipaddr,53))
2381 if trynum+1 <= self.notify_retries:
2382 self.sentnotify.append((origin,ipaddr,trynum+1,curtime))
2385 def handle_packet(self, msgdata, addr, server):
2388 msg = message(msgdata)
2391 # find a matching view
2392 (zkeys, dorecursion, flist) = self.config.getview(msg, addr[0], addr[1])
2393 if not msg.header.qr and msg.header.opcode == 5:
2394 log(2,'GOT UPDATE PACKET')
2395 # check the zone section
2396 if (msg.header.zocount != 1 or
2397 msg.zone.ztype != 'SOA' or
2398 msg.zone.zclass != 'IN'):
2399 log(2,'SENDING FORMERR UPDATE ERROR')
2400 errormsg = self.error(msg.header.id, msg.zone.zname,
2401 msg.zone.ztype, msg.zone.zclass, 1)
2402 server.sendpackets([errormsg],addr)
2404 (answer, origin, slaves) = self.zdb.handle_update(msg, addr, self)
2405 if answer.header.rcode == 0:
2406 # schedule NOTIFYs to slaves
2407 for ipaddr in slaves:
2408 self.notifys.append((origin, ipaddr, 0))
2409 server.sendpackets([answer],addr)
2410 elif msg.header.opcode == 4:
2412 log(0,'got NOTIFY response')
2413 for origin, ipaddr, trynum, senttime in self.sentnotify:
2414 if ipaddr == addr[0] and msg.question.qname == origin:
2415 self.sentnotify.remove((origin, ipaddr, trynum, senttime))
2418 self.process_notify(msg, addr[0], addr[1])
2419 elif not msg.header.qr and msg.header.opcode == 0:
2421 qname = msg.question.qname.lower()
2422 log(2,'GOT QUERY for ' + qname + '(' + msg.question.qtype +
2423 ') from ' + addr[0])
2424 # handle special version packet
2425 if (msg.question.qtype == 'TXT' and
2426 msg.question.qclass == 'CH'):
2427 if qname == 'version.bind':
2428 server.sendpackets([getversion(qname,
2431 dorecursion, '1.0')],addr)
2432 elif qname == 'version.oak':
2433 server.sendpackets([getversion(qname,
2436 dorecursion, '1.0')],addr)
2438 self.zdb.lookup(zkeys, msg, addr, server, dorecursion,
2439 flist, self.lookup_callback)
2441 def lookup_callback(self, msg, addr, server, dorecursion, flist, answerlist):
2443 server.sendpackets(self.config.outpackets(answerlist), addr)
2445 if msg.question.qtype in ['AXFR','IXFR']:
2446 if msg.question.qname == 'cache' and msg.question.qtype == 'AXFR':
2448 server.sendpackets(self.resolver.cache.zonetrans(msg.header.id),addr)
2450 # won't forward zone transfers and
2451 # don't handle recursive zone transfers
2452 server.sendpackets([self.error(msg.header.id, msg.question.qname,
2454 msg.question.qclass,2)],addr)
2456 self.resolver.handle_query(msg, addr, flist, server.sendpackets)
2459 log(4,'in nameserver reap')
2460 # do all maintenence (interval) stuff here
2462 self.resolver.reap()
2464 curtime = time.time()
2465 if curtime > (self.last_reap_time + self.maint_int):
2466 self.last_reap_time = curtime
2467 # do zone transfers here if slave server and haven't asked for soa
2468 for (zkey, origin, masterip) in self.zdb.getslaves(curtime):
2469 if not self.askedsoa.has_key(zkey):
2470 self.need_zonetransfer(zkey, origin, masterip)
2471 for zkey in self.askedsoa.keys():
2472 if curtime > self.askedsoa[zkey]['senttime'] + self.soatimeout:
2473 if self.askedsoa[zkey]['trynum'] > 3:
2474 self.zdb.remove_zone(zkey)
2475 del self.askedsoa[zkey]
2477 masterip = self.askedsoa[zkey]['masterip']
2478 origin = self.askedsoa[zkey]['origin']
2479 trynum = self.askedsoa[zkey]['trynum']
2480 del self.askedsoa[zkey]
2481 self.need_zonetransfer(zkey, origin, masterip, trynum)
2483 def log_info (self, message, type='info'):
2484 if __debug__ or type != 'info':
2485 log(0,'%s: %s' % (type, message))
2487 class resolver(asyncore.dispatcher):
2488 def __init__(self, cache, port=0):
2489 asyncore.dispatcher.__init__(self)
2490 self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
2491 self.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 200 * 1024)
2492 self.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 200 * 1024)
2493 self.bind(('',port))
2499 self.holdqlength = 100
2500 self.last_reap_time = time.time()
2504 def getoutqkey(self):
2505 self.outqnum = self.outqnum + 1
2506 if self.outqnum == 99999:
2508 return str(self.outqnum)
2510 def error(self, id, qname, querytype, queryclass, rcode):
2512 error.header.id = id
2513 error.header.rcode = rcode
2515 error.question.qname = qname
2516 error.question.qtype = querytype
2517 error.question.qclass = queryclass
2520 def qpacket(self, id, qname, querytype, queryclass):
2523 query.header.id = id
2525 query.question.qname = qname
2526 query.question.qtype = querytype
2527 query.question.qclass = queryclass
2530 def send_to(self, msglist, addr):
2532 data = msg.buildpkt()
2536 msg.header.ancount = 0
2538 msg.header.nscount = 0
2540 msg.header.arcount = 0
2542 self.socket.sendto(msg.buildpkt(), addr)
2544 self.socket.sendto(data, addr)
2546 def handle_read(self):
2549 msgdata, addr = self.socket.recvfrom(1500)
2550 # should put 'try' here in production server
2551 self.handle_packet(msgdata, addr)
2552 except socket.error, why:
2553 if why[0] != asyncore.EWOULDBLOCK:
2554 raise socket.error, why
2556 def handle_packet(self, msgdata, addr):
2558 msg = message(msgdata)
2561 if not msg.header.qr:
2562 self.handle_query(msg, addr, [], self.send_to)
2564 log(2,'received unsolicited reply')
2567 def handle_query(self, msg, addr, flist, cbfunc):
2568 qname = msg.question.qname
2569 querytype = msg.question.qtype
2570 queryclass = msg.question.qclass
2571 # check the cache first
2572 answer = self.cache.haskey(qname,querytype,msg)
2574 cbfunc([answer], addr)
2575 log(2,'sent answer for ' + qname + '(' + querytype +
2578 # check if query is already in progess
2579 for oqkey in self.outq.keys():
2580 if (self.outq[oqkey]['qname'] == qname and
2581 self.outq[oqkey]['querytype'] == querytype):
2582 log(2,'query already in progress for '+qname+'('+querytype+')')
2583 # put entry in hold queue to try later
2584 hqrec = {'processtime':time.time()+self.holdtime,
2585 'query':msg,'addr':addr,
2586 'qname':qname,'querytype':querytype,
2587 'queryclass':queryclass,
2589 self.putonhold(hqrec)
2592 outqkey = self.getoutqkey()+str(msg.header.id)
2593 self.outq[outqkey] = {'query':msg,
2596 'querytype':querytype,
2597 'queryclass':queryclass,
2603 self.outq[outqkey]['flist'] = flist
2604 self.askfns(outqkey)
2608 def putonhold(self,hqrec):
2609 hqid = hqrec['qname']+hqrec['querytype']
2610 if self.holdq.has_key(hqid):
2611 if len(self.holdq[hqid]) < self.holdqlength:
2612 hqrec['processtime']=time.time()+self.holdtime
2613 self.holdq[hqid].append(hqrec)
2616 def askns(self, outqkey):
2617 qname = self.outq[outqkey]['qname']
2618 querytype = self.outq[outqkey]['querytype']
2619 queryclass = self.outq[outqkey]['queryclass']
2620 # don't try more than 10 times to avoid loops
2621 if self.outq[outqkey]['qsent'] == 10:
2622 del self.outq[outqkey]
2623 log(2,'Dropping query for ' + qname + '(' + querytype + ')' +
2626 # find the best nameservers to ask from the cache
2627 (qzone, nsdict) = self.cache.getnslist(qname)
2629 # there are no good servers
2630 if self.outq[outqkey]['addr'] != 'IQ':
2631 qid = self.outq[outqkey]['query'].header.id
2632 self.outq[outqkey]['cbfunc'](self.error(qid,qname,querytype,queryclass,2),
2633 self.outq[outqkey]['addr'])
2634 del self.outq[outqkey]
2635 log(2,'Dropping query for ' + qname + '(' + querytype + ')' +
2636 'no good name servers to ask')
2638 # pick the best nameserver
2639 rtts = nsdict.keys()
2641 bestnsip = nsdict[rtts[0]]['ip']
2642 bestnsname = nsdict[rtts[0]]['name']
2643 # fill in the callback data structure
2644 id=random.randrange(1,32768)
2645 self.outq[outqkey]['nsqueriedlastip'] = bestnsip
2646 self.outq[outqkey]['nsqueriedlastname'] = bestnsname
2647 self.outq[outqkey]['nsdict'] = nsdict
2648 self.outq[outqkey]['qzone'] = qzone
2649 self.outq[outqkey]['qsenttime'] = time.time()
2650 self.outq[outqkey]['qsent'] = self.outq[outqkey]['qsent'] + 1
2651 # self.socket.sendto(self.qpacket(id,qname,querytype,queryclass), (bestnsip,53))
2652 self.outq[outqkey]['request'] = simpleudprequest(self.qpacket(id,qname,querytype,queryclass),
2653 self.handle_response, bestnsip, outqkey)
2654 # update rtt so that we ask a different server next time
2655 self.cache.updatertt(bestnsname,qzone,1)
2656 log(2,outqkey+'|sent query to ' + bestnsip + '(' + bestnsname +
2657 ') for ' + qname + '(' + querytype + ')')
2659 def askfns(self, outqkey):
2660 flist = self.outq[outqkey]['flist']
2661 qname = self.outq[outqkey]['qname']
2662 querytype = self.outq[outqkey]['querytype']
2663 queryclass = self.outq[outqkey]['queryclass']
2664 self.outq[outqkey]['qsenttime'] = time.time()
2665 id=random.randrange(1,32768)
2666 # self.socket.sendto(self.qpacket(id,qname,querytype,queryclass), (flist[0],53))
2667 self.outq[outqkey]['request'] = simpleudprequest(self.qpacket(id,qname,querytype,queryclass),
2668 self.handle_fresponse, flist[0], outqkey)
2669 log(2,''+outqkey+'|sent query to forwarder')
2671 def handle_response(self, msg, outqkey):
2673 # 1. contains a name error
2674 # 2. answers the question
2675 # (cache data and return it)
2676 # 3. is (contains) a CNAME and qtype isn't
2677 # (cache cname and change qname to it)
2678 # (check if qname and qtype are in any other rrs in the response)
2679 # (must check cache again here)
2680 # 4. contains a better delegation
2681 # (cache the delegation and start again)
2682 # 5. is aserver failure
2683 # (delete server from list and try again)
2685 # make sure that original question is still outstanding
2686 if not self.outq.has_key(outqkey):
2687 # should never get here
2688 # if we do we aren't doing housekeeping of callbacks very well
2689 log(2,''+outqkey+'|got response for a question already answered for ' + msg.question.qname)
2692 querytype = self.outq[outqkey]['querytype']
2693 if msg.header.rcode not in [1,2,4,5]:
2695 rtt = time.time() - self.outq[outqkey]['qsenttime']
2696 nsname = self.outq[outqkey]['nsqueriedlastname']
2697 zone = self.outq[outqkey]['qzone']
2698 self.cache.updatertt(nsname,zone,rtt)
2700 if msg.header.rcode == 3:
2701 log(2,outqkey+'|GOT Name Error for ' + msg.question.qname +
2702 '(' + msg.question.qtype + ')')
2704 # cache negative answer
2705 self.cache.addneg(self.outq[outqkey]['qname'],
2706 self.outq[outqkey]['querytype'],
2707 self.outq[outqkey]['queryclass'])
2708 if self.outq[outqkey]['addr'] != 'IQ':
2710 answer.question.qname = self.outq[outqkey]['query'].question.qname
2711 answer.question.qtype = self.outq[outqkey]['query'].question.qtype
2712 answer.question.qclass = self.outq[outqkey]['query'].question.qclass
2713 answer.header.id = self.outq[outqkey]['query'].header.id
2714 answer.header.qr = 1
2715 answer.header.opcode = self.outq[outqkey]['query'].header.opcode
2716 answer.header.ra = 1
2717 self.outq[outqkey]['cbfunc']([answer], self.outq[outqkey]['addr'])
2718 del self.outq[outqkey]
2720 elif msg.header.ancount > 0:
2721 # answer (may be CNAME)
2724 log(2,'CACHING ANSWERLIST ENTRIES')
2725 for rr in msg.answerlist:
2726 rrname = rr.keys()[0]
2727 rrtype = rr[rrname].keys()[0]
2728 if ((rrname == msg.question.qname or rrname == cname ) and
2729 rrtype == msg.question.qtype):
2731 if rrname == msg.question.qname and rrtype == 'CNAME':
2732 cname = rr[rrname][rrtype][0]['cname']
2733 self.cache.add(rr, self.outq[outqkey]['qzone'],
2734 self.outq[outqkey]['nsqueriedlastname'])
2736 if self.outq[outqkey]['addr'] != 'IQ':
2737 log(2,''+outqkey+'|GOT Answer for ' + msg.question.qname +
2738 '(' + msg.question.qtype + ')' )
2740 answer.answerlist = msg.answerlist + self.outq[outqkey]['answerlist']
2741 answer.header.ancount = len(answer.answerlist)
2742 answer.question.qname = self.outq[outqkey]['query'].question.qname
2743 answer.question.qtype = self.outq[outqkey]['query'].question.qtype
2744 answer.question.qclass = self.outq[outqkey]['query'].question.qclass
2745 answer.header.id = self.outq[outqkey]['query'].header.id
2746 answer.header.qr = 1
2747 answer.header.opcode = self.outq[outqkey]['query'].header.opcode
2748 answer.header.ra = 1
2749 self.outq[outqkey]['cbfunc']([answer], self.outq[outqkey]['addr'])
2750 log(2,outqkey+'|sent answer retrieved from remote server for ' +
2751 self.outq[outqkey]['query'].question.qname)
2753 log(2,outqkey+'|GOT Answer(IQ) for ' + msg.question.qname + '(' +
2754 msg.question.qtype + ')')
2755 del self.outq[outqkey]
2757 log(2,outqkey+'|GOT CNAME for ' + msg.question.qname + '(' + msg.question.qtype + ')')
2758 self.outq[outqkey]['answerlist'] = self.outq[outqkey]['answerlist'] + msg.answerlist
2759 self.outq[outqkey]['qname'] = cname
2762 log(2,outqkey+'|GOT BOGUS answer for ' + msg.question.qname + '(' +
2763 msg.question.qtype + ')')
2764 del self.outq[outqkey]
2766 elif msg.header.nscount > 0 and msg.header.ancount == 0:
2767 log(2,outqkey+'|GOT DELEGATION for ' + msg.question.qname + '(' + msg.question.qtype + ')')
2769 # cache the nameserver rrs and start over
2770 # if there are no glue records for nameservers must fetch them first
2771 log(2,'CACHING AUTHLIST ENTRIES')
2772 for rr in msg.authlist:
2773 self.cache.add(rr,self.outq[outqkey]['qzone'],self.outq[outqkey]['nsqueriedlastname'])
2774 log(2,'CACHING ADDLIST ENTRIES')
2775 for rr in msg.addlist:
2776 self.cache.add(rr,self.outq[outqkey]['qzone'],self.outq[outqkey]['nsqueriedlastname'])
2777 rrlist = msg.authlist+msg.addlist
2780 for rr in msg.authlist:
2781 nodename = rr.keys()[0]
2782 if rr[nodename].keys()[0] == 'NS':
2783 nscount = nscount + 1
2784 nsdname = rr[nodename]['NS'][0]['nsdname']
2785 if not self.cache.haskey(nsdname,'A'):
2786 log(2,outqkey+'|Glue record not in cache for ' + nsdname + '(A)')
2787 fetchglue = fetchglue + 1
2788 # need to fetch A rec
2789 noutqkey = self.getoutqkey()+str(random.randrange(1,32768))
2790 self.outq[noutqkey] = {'query':'',
2796 log(2,outqkey+'|sending a query to fetch glue records for ' + nsdname + '(A)')
2797 self.askns(noutqkey)
2799 log(2,outqkey+'|Dropping query (no ns recs) for ' +
2800 msg.question.qname + '(' + msg.question.qtype + ')' )
2801 del self.outq[outqkey]
2802 elif fetchglue == nscount:
2803 log(2,outqkey+'|Stalling query (no glue recs) for ' +
2804 msg.question.qname + '(' + msg.question.qtype + ')')
2805 self.putonhold(self.outq[outqkey])
2806 del self.outq[outqkey]
2808 log(2,outqkey+'|got (some) glue with delegation')
2811 elif msg.header.rcode in [1,2,4,5]:
2812 log(2,outqkey+'|GOT ' + getrcode(msg.header.rcode))
2813 log(2,'SERVER ' + self.outq[outqkey]['nsqueriedlastname'] + '(' +
2814 self.outq[outqkey]['nsqueriedlastip'] + ') FAILURE for ' + msg.question.qname)
2815 # don't ask this server for a while
2816 self.cache.badns(self.outq[outqkey]['qzone'],self.outq[outqkey]['nsqueriedlastname'])
2819 log(2,outqkey+'|GOT UNPARSEABLE REPLY')
2822 def handle_fresponse(self, msg, outqkey):
2823 if msg.header.rcode in [1,2,4,5]:
2824 self.outq[outqkey]['flist'].pop(0)
2825 if len(self.outq[outqkey]['flist']) == 0:
2826 qid = self.outq[outqkey]['query'].header.id
2827 qname = self.outq[outqkey]['qname']
2828 querytype = self.outq[outqkey]['querytype']
2829 queryclass = self.outq[outqkey]['queryclass']
2830 self.outq[outqkey]['cbfunc'](self.error(qid,qname,querytype,queryclass,2),
2831 self.outq[outqkey]['addr'])
2832 del self.outq[outqkey]
2834 self.askfns(outqkey)
2837 answer.header.id = self.outq[outqkey]['query'].header.id
2838 answer.header.qr = 1
2839 answer.header.opcode = self.outq[outqkey]['query'].header.opcode
2840 answer.header.ra = 1
2841 answer.question.qname = self.outq[outqkey]['query'].question.qname
2842 answer.question.qtype = self.outq[outqkey]['query'].question.qtype
2843 answer.question.qclass = self.outq[outqkey]['query'].question.qclass
2844 answer.header.ancount = msg.header.ancount
2845 answer.header.nscount = msg.header.nscount
2846 answer.header.arcount = msg.header.arcount
2847 answer.answerlist = msg.answerlist
2848 answer.authlist = msg.authlist
2849 answer.addlist = msg.addlist
2850 if msg.header.rcode == 3:
2852 # cache negative answer
2853 self.cache.addneg(self.outq[outqkey]['qname'],
2854 self.outq[outqkey]['querytype'],
2855 self.outq[outqkey]['queryclass'])
2858 for rr in msg.answerlist:
2859 self.cache.add(rr,'','forwarder')
2860 for rr in msg.authlist:
2861 self.cache.add(rr,'','forwarder')
2862 for rr in msg.addlist:
2863 self.cache.add(rr,'','forwarder')
2864 self.outq[outqkey]['cbfunc']([answer], self.outq[outqkey]['addr'])
2865 del self.outq[outqkey]
2870 def handle_write(self):
2873 def handle_connect(self):
2876 def handle_close(self):
2877 # print '1:In handle close'
2880 def process_holdq(self):
2881 curtime = time.time()
2882 for hqkey in self.holdq.keys():
2883 for hqrec in self.holdq[hqkey]:
2884 if curtime >= hqrec['processtime']:
2885 log(2,'processing held query')
2886 answer = self.cache.haskey(hqrec['qname'],
2890 hqrec['cbfunc']([answer], hqrec['addr'])
2891 log(2,'sent answer for ' + hqrec['qname'] +
2892 '(' + hqrec['querytype'] + ') from cache')
2893 self.holdq[hqkey].remove(hqrec)
2894 if len(self.holdq[hqkey]) == 0:
2895 del self.holdq[hqkey]
2898 self.process_holdq()
2899 curtime = time.time()
2900 log(3,timestamp() + 'processed HOLDQ (sockets: ' +
2901 str(len(asyncore.socket_map.keys()))+')')
2902 if curtime > (self.last_reap_time + self.maint_int):
2903 self.last_reap_time = curtime
2904 for outqkey in self.outq.keys():
2905 if curtime > self.outq[outqkey]['qsenttime'] + self.timeout:
2906 log(2,'query for '+self.outq[outqkey]['qname']+'('+
2907 self.outq[outqkey]['querytype']+') expired')
2908 # don't set forwarders as bad
2909 if not self.outq[outqkey].has_key('flist'):
2910 self.cache.badns(self.outq[outqkey]['qzone'],
2911 self.outq[outqkey]['nsqueriedlastname'])
2912 if self.outq[outqkey].has_key('request'):
2913 log(3,'closing socket for expired query')
2914 self.outq[outqkey]['request'].close()
2915 del self.outq[outqkey]
2918 def log_info (self, message, type='info'):
2919 if __debug__ or type != 'info':
2920 log(0,'%s: %s' % (type, message))
2925 r = resolver(dnscache(configobj.cached))
2926 ns = nameserver(r, configobj)
2927 udpds = udpdnsserver(53, ns)
2928 tcpds = tcpdnsserver(53, ns)
2929 loglevel = configobj.loglevel
2932 except KeyboardInterrupt:
2935 if __name__ == '__main__':
2936 sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
2937 zonedict = {'example.net':{'origin':'example.net',
2938 'filename':'db.example.net',
2943 zonedict = {'servers.csail.mit.edu':{'origin':'servers.csail.mit.edu',
2944 'filename':'db.servers.csail.mit.edu',
2948 zonedict2 = {'example.net':{'origin':'example.net',
2949 'filename':'db.example.net',
2951 'masterip':'127.0.0.1'}}
2952 readzonefiles(zonedict)
2953 lconfig = dnsconfig()
2954 lconfig.zonedatabase = zonedb(zonedict)
2955 pr = zonefileparser()
2956 pr.parse('','db.ca')
2957 lconfig.cached = pr.getzdict()
2958 lconfig.loglevel = 3