Add bw, dns, and uptime checks.
[myops.git] / web / collect / client / DNS / Lib.py
1 # -*- encoding: utf-8 -*-
2 """
3  $Id: Lib.py,v 1.11.2.8 2011/03/16 20:06:39 customdesigned Exp $
4
5  This file is part of the pydns project.
6  Homepage: http://pydns.sourceforge.net
7
8  This code is covered by the standard Python License. See LICENSE for details.
9
10  Library code. Largely this is packers and unpackers for various types.
11 """
12
13 #
14 #
15 # See RFC 1035:
16 # ------------------------------------------------------------------------
17 # Network Working Group                                     P. Mockapetris
18 # Request for Comments: 1035                                           ISI
19 #                                                            November 1987
20 # Obsoletes: RFCs 882, 883, 973
21 #
22 #             DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
23 # ------------------------------------------------------------------------
24
25
26 import string, types
27
28 import Type
29 import Class
30 import Opcode
31 import Status
32 import DNS
33
34 from Base import DNSError
35
36 LABEL_UTF8 = False
37 LABEL_ENCODING = 'idna'
38
39 class UnpackError(DNSError): pass
40 class PackError(DNSError): pass
41
42 # Low-level 16 and 32 bit integer packing and unpacking
43
44 from struct import pack as struct_pack
45 from struct import unpack as struct_unpack
46 from socket import inet_ntoa, inet_aton
47
48 def pack16bit(n):
49     return struct_pack('!H', n)
50
51 def pack32bit(n):
52     return struct_pack('!L', n)
53
54 def unpack16bit(s):
55     return struct_unpack('!H', s)[0]
56
57 def unpack32bit(s):
58     return struct_unpack('!L', s)[0]
59
60 def addr2bin(addr):
61     return struct_unpack('!l', inet_aton(addr))[0]
62
63 def bin2addr(n):
64     return inet_ntoa(struct_pack('!L', n))
65
66 # Packing class
67
68 class Packer:
69     " packer base class. supports basic byte/16bit/32bit/addr/string/name "
70     def __init__(self):
71         self.buf = ''
72         self.index = {}
73     def getbuf(self):
74         return self.buf
75     def addbyte(self, c):
76         if len(c) != 1: raise TypeError, 'one character expected'
77         self.buf = self.buf + c
78     def addbytes(self, bytes):
79         self.buf = self.buf + bytes
80     def add16bit(self, n):
81         self.buf = self.buf + pack16bit(n)
82     def add32bit(self, n):
83         self.buf = self.buf + pack32bit(n)
84     def addaddr(self, addr):
85         n = addr2bin(addr)
86         self.buf = self.buf + pack32bit(n)
87     def addstring(self, s):
88         if len(s) > 255:
89             raise ValueError, "Can't encode string of length "+ \
90                             "%s (> 255)"%(len(s))
91         self.addbyte(chr(len(s)))
92         self.addbytes(s)
93     def addname(self, name):
94         # Domain name packing (section 4.1.4)
95         # Add a domain name to the buffer, possibly using pointers.
96         # The case of the first occurrence of a name is preserved.
97         # Redundant dots are ignored.
98         list = []
99         for label in string.splitfields(name, '.'):
100             if not label:
101                 raise PackError, 'empty label'
102             list.append(label)
103         keys = []
104         for i in range(len(list)):
105             key = string.upper(string.joinfields(list[i:], '.'))
106             keys.append(key)
107             if self.index.has_key(key):
108                 pointer = self.index[key]
109                 break
110         else:
111             i = len(list)
112             pointer = None
113         # Do it into temporaries first so exceptions don't
114         # mess up self.index and self.buf
115         buf = ''
116         offset = len(self.buf)
117         index = []
118         if DNS.LABEL_UTF8:
119           enc = 'utf8'
120         else:
121           enc = DNS.LABEL_ENCODING
122         for j in range(i):
123             label = list[j]
124             try:
125                 label = label.encode(enc)
126             except UnicodeEncodeError:
127                 if not DNS.LABEL_UTF8: raise
128                 if not label.startswith('\ufeff'):
129                     label = '\ufeff'+label
130                 label = label.encode(enc)
131             n = len(label)
132             if n > 63:
133                 raise PackError, 'label too long'
134             if offset + len(buf) < 0x3FFF:
135                 index.append((keys[j], offset + len(buf)))
136             else:
137                 print 'DNS.Lib.Packer.addname:',
138                 print 'warning: pointer too big'
139             buf = buf + (chr(n) + label)
140         if pointer:
141             buf = buf + pack16bit(pointer | 0xC000)
142         else:
143             buf = buf + '\0'
144         self.buf = self.buf + buf
145         for key, value in index:
146             self.index[key] = value
147     def dump(self):
148         keys = self.index.keys()
149         keys.sort()
150         print '-'*40
151         for key in keys:
152             print '%20s %3d' % (key, self.index[key])
153         print '-'*40
154         space = 1
155         for i in range(0, len(self.buf)+1, 2):
156             if self.buf[i:i+2] == '**':
157                 if not space: print
158                 space = 1
159                 continue
160             space = 0
161             print '%4d' % i,
162             for c in self.buf[i:i+2]:
163                 if ' ' < c < '\177':
164                     print ' %c' % c,
165                 else:
166                     print '%2d' % ord(c),
167             print
168         print '-'*40
169
170
171 # Unpacking class
172
173
174 class Unpacker:
175     def __init__(self, buf):
176         self.buf = buf
177         self.offset = 0
178     def getbyte(self):
179         if self.offset >= len(self.buf):
180             raise UnpackError, "Ran off end of data"
181         c = self.buf[self.offset]
182         self.offset = self.offset + 1
183         return c
184     def getbytes(self, n):
185         s = self.buf[self.offset : self.offset + n]
186         if len(s) != n: raise UnpackError, 'not enough data left'
187         self.offset = self.offset + n
188         return s
189     def get16bit(self):
190         return unpack16bit(self.getbytes(2))
191     def get32bit(self):
192         return unpack32bit(self.getbytes(4))
193     def getaddr(self):
194         return bin2addr(self.get32bit())
195     def getstring(self):
196         return self.getbytes(ord(self.getbyte()))
197     def getname(self):
198         # Domain name unpacking (section 4.1.4)
199         c = self.getbyte()
200         i = ord(c)
201         if i & 0xC0 == 0xC0:
202             d = self.getbyte()
203             j = ord(d)
204             pointer = ((i<<8) | j) & ~0xC000
205             save_offset = self.offset
206             try:
207                 self.offset = pointer
208                 domain = self.getname()
209             finally:
210                 self.offset = save_offset
211             return domain
212         if i == 0:
213             return ''
214         domain = self.getbytes(i)
215         remains = self.getname()
216         if not remains:
217             return domain
218         else:
219             return domain + '.' + remains
220
221
222 # Test program for packin/unpacking (section 4.1.4)
223
224 def testpacker():
225     N = 2500
226     R = range(N)
227     import timing
228     # See section 4.1.4 of RFC 1035
229     timing.start()
230     for i in R:
231         p = Packer()
232         p.addaddr('192.168.0.1')
233         p.addbytes('*' * 20)
234         p.addname('f.ISI.ARPA')
235         p.addbytes('*' * 8)
236         p.addname('Foo.F.isi.arpa')
237         p.addbytes('*' * 18)
238         p.addname('arpa')
239         p.addbytes('*' * 26)
240         p.addname('')
241     timing.finish()
242     print timing.milli(), "ms total for packing"
243     print round(timing.milli()  / i, 4), 'ms per packing'
244     #p.dump()
245     u = Unpacker(p.buf)
246     u.getaddr()
247     u.getbytes(20)
248     u.getname()
249     u.getbytes(8)
250     u.getname()
251     u.getbytes(18)
252     u.getname()
253     u.getbytes(26)
254     u.getname()
255     timing.start()
256     for i in R:
257         u = Unpacker(p.buf)
258
259         res = (u.getaddr(),
260                u.getbytes(20),
261                u.getname(),
262                u.getbytes(8),
263                u.getname(),
264                u.getbytes(18),
265                u.getname(),
266                u.getbytes(26),
267                u.getname())
268     timing.finish()
269     print timing.milli(), "ms total for unpacking"
270     print round(timing.milli() / i, 4), 'ms per unpacking'
271     #for item in res: print item
272
273
274 # Pack/unpack RR toplevel format (section 3.2.1)
275
276 class RRpacker(Packer):
277     def __init__(self):
278         Packer.__init__(self)
279         self.rdstart = None
280     def addRRheader(self, name, type, klass, ttl, *rest):
281         self.addname(name)
282         self.add16bit(type)
283         self.add16bit(klass)
284         self.add32bit(ttl)
285         if rest:
286             if rest[1:]: raise TypeError, 'too many args'
287             rdlength = rest[0]
288         else:
289             rdlength = 0
290         self.add16bit(rdlength)
291         self.rdstart = len(self.buf)
292     def patchrdlength(self):
293         rdlength = unpack16bit(self.buf[self.rdstart-2:self.rdstart])
294         if rdlength == len(self.buf) - self.rdstart:
295             return
296         rdata = self.buf[self.rdstart:]
297         save_buf = self.buf
298         ok = 0
299         try:
300             self.buf = self.buf[:self.rdstart-2]
301             self.add16bit(len(rdata))
302             self.buf = self.buf + rdata
303             ok = 1
304         finally:
305             if not ok: self.buf = save_buf
306     def endRR(self):
307         if self.rdstart is not None:
308             self.patchrdlength()
309         self.rdstart = None
310     def getbuf(self):
311         if self.rdstart is not None: self.patchrdlength()
312         return Packer.getbuf(self)
313     # Standard RRs (section 3.3)
314     def addCNAME(self, name, klass, ttl, cname):
315         self.addRRheader(name, Type.CNAME, klass, ttl)
316         self.addname(cname)
317         self.endRR()
318     def addHINFO(self, name, klass, ttl, cpu, os):
319         self.addRRheader(name, Type.HINFO, klass, ttl)
320         self.addstring(cpu)
321         self.addstring(os)
322         self.endRR()
323     def addMX(self, name, klass, ttl, preference, exchange):
324         self.addRRheader(name, Type.MX, klass, ttl)
325         self.add16bit(preference)
326         self.addname(exchange)
327         self.endRR()
328     def addNS(self, name, klass, ttl, nsdname):
329         self.addRRheader(name, Type.NS, klass, ttl)
330         self.addname(nsdname)
331         self.endRR()
332     def addPTR(self, name, klass, ttl, ptrdname):
333         self.addRRheader(name, Type.PTR, klass, ttl)
334         self.addname(ptrdname)
335         self.endRR()
336     def addSOA(self, name, klass, ttl,
337               mname, rname, serial, refresh, retry, expire, minimum):
338         self.addRRheader(name, Type.SOA, klass, ttl)
339         self.addname(mname)
340         self.addname(rname)
341         self.add32bit(serial)
342         self.add32bit(refresh)
343         self.add32bit(retry)
344         self.add32bit(expire)
345         self.add32bit(minimum)
346         self.endRR()
347     def addTXT(self, name, klass, ttl, list):
348         self.addRRheader(name, Type.TXT, klass, ttl)
349         if type(list) is types.StringType:
350             list = [list]
351         for txtdata in list:
352             self.addstring(txtdata)
353         self.endRR()
354     # Internet specific RRs (section 3.4) -- class = IN
355     def addA(self, name, klass, ttl, address):
356         self.addRRheader(name, Type.A, klass, ttl)
357         self.addaddr(address)
358         self.endRR()
359     def addWKS(self, name, ttl, address, protocol, bitmap):
360         self.addRRheader(name, Type.WKS, Class.IN, ttl)
361         self.addaddr(address)
362         self.addbyte(chr(protocol))
363         self.addbytes(bitmap)
364         self.endRR()
365     def addSRV(self):
366         raise NotImplementedError
367
368 def prettyTime(seconds):
369     if seconds<60:
370         return seconds,"%d seconds"%(seconds)
371     if seconds<3600:
372         return seconds,"%d minutes"%(seconds/60)
373     if seconds<86400:
374         return seconds,"%d hours"%(seconds/3600)
375     if seconds<604800:
376         return seconds,"%d days"%(seconds/86400)
377     else:
378         return seconds,"%d weeks"%(seconds/604800)
379
380
381 class RRunpacker(Unpacker):
382     def __init__(self, buf):
383         Unpacker.__init__(self, buf)
384         self.rdend = None
385     def getRRheader(self):
386         name = self.getname()
387         rrtype = self.get16bit()
388         klass = self.get16bit()
389         ttl = self.get32bit()
390         rdlength = self.get16bit()
391         self.rdend = self.offset + rdlength
392         return (name, rrtype, klass, ttl, rdlength)
393     def endRR(self):
394         if self.offset != self.rdend:
395             raise UnpackError, 'end of RR not reached'
396     def getCNAMEdata(self):
397         return self.getname()
398     def getHINFOdata(self):
399         return self.getstring(), self.getstring()
400     def getMXdata(self):
401         return self.get16bit(), self.getname()
402     def getNSdata(self):
403         return self.getname()
404     def getPTRdata(self):
405         return self.getname()
406     def getSOAdata(self):
407         return self.getname(), \
408                self.getname(), \
409                ('serial',)+(self.get32bit(),), \
410                ('refresh ',)+prettyTime(self.get32bit()), \
411                ('retry',)+prettyTime(self.get32bit()), \
412                ('expire',)+prettyTime(self.get32bit()), \
413                ('minimum',)+prettyTime(self.get32bit())
414     def getTXTdata(self):
415         list = []
416         while self.offset != self.rdend:
417             list.append(self.getstring())
418         return list
419     getSPFdata = getTXTdata
420     def getAdata(self):
421         return self.getaddr()
422     def getWKSdata(self):
423         address = self.getaddr()
424         protocol = ord(self.getbyte())
425         bitmap = self.getbytes(self.rdend - self.offset)
426         return address, protocol, bitmap
427     def getSRVdata(self):
428         """
429         _Service._Proto.Name TTL Class SRV Priority Weight Port Target
430         """
431         priority = self.get16bit()
432         weight = self.get16bit()
433         port = self.get16bit()
434         target = self.getname()
435         #print '***priority, weight, port, target', priority, weight, port, target
436         return priority, weight, port, target
437
438
439 # Pack/unpack Message Header (section 4.1)
440
441 class Hpacker(Packer):
442     def addHeader(self, id, qr, opcode, aa, tc, rd, ra, z, rcode,
443               qdcount, ancount, nscount, arcount):
444         self.add16bit(id)
445         self.add16bit((qr&1)<<15 | (opcode&0xF)<<11 | (aa&1)<<10
446                   | (tc&1)<<9 | (rd&1)<<8 | (ra&1)<<7
447                   | (z&7)<<4 | (rcode&0xF))
448         self.add16bit(qdcount)
449         self.add16bit(ancount)
450         self.add16bit(nscount)
451         self.add16bit(arcount)
452
453 class Hunpacker(Unpacker):
454     def getHeader(self):
455         id = self.get16bit()
456         flags = self.get16bit()
457         qr, opcode, aa, tc, rd, ra, z, rcode = (
458                   (flags>>15)&1,
459                   (flags>>11)&0xF,
460                   (flags>>10)&1,
461                   (flags>>9)&1,
462                   (flags>>8)&1,
463                   (flags>>7)&1,
464                   (flags>>4)&7,
465                   (flags>>0)&0xF)
466         qdcount = self.get16bit()
467         ancount = self.get16bit()
468         nscount = self.get16bit()
469         arcount = self.get16bit()
470         return (id, qr, opcode, aa, tc, rd, ra, z, rcode,
471                   qdcount, ancount, nscount, arcount)
472
473
474 # Pack/unpack Question (section 4.1.2)
475
476 class Qpacker(Packer):
477     def addQuestion(self, qname, qtype, qclass):
478         self.addname(qname)
479         self.add16bit(qtype)
480         self.add16bit(qclass)
481
482 class Qunpacker(Unpacker):
483     def getQuestion(self):
484         return self.getname(), self.get16bit(), self.get16bit()
485
486
487 # Pack/unpack Message(section 4)
488 # NB the order of the base classes is important for __init__()!
489
490 class Mpacker(RRpacker, Qpacker, Hpacker):
491     pass
492
493 class Munpacker(RRunpacker, Qunpacker, Hunpacker):
494     pass
495
496
497 # Routines to print an unpacker to stdout, for debugging.
498 # These affect the unpacker's current position!
499
500 def dumpM(u):
501     print 'HEADER:',
502     (id, qr, opcode, aa, tc, rd, ra, z, rcode,
503               qdcount, ancount, nscount, arcount) = u.getHeader()
504     print 'id=%d,' % id,
505     print 'qr=%d, opcode=%d, aa=%d, tc=%d, rd=%d, ra=%d, z=%d, rcode=%d,' \
506               % (qr, opcode, aa, tc, rd, ra, z, rcode)
507     if tc: print '*** response truncated! ***'
508     if rcode: print '*** nonzero error code! (%d) ***' % rcode
509     print '  qdcount=%d, ancount=%d, nscount=%d, arcount=%d' \
510               % (qdcount, ancount, nscount, arcount)
511     for i in range(qdcount):
512         print 'QUESTION %d:' % i,
513         dumpQ(u)
514     for i in range(ancount):
515         print 'ANSWER %d:' % i,
516         dumpRR(u)
517     for i in range(nscount):
518         print 'AUTHORITY RECORD %d:' % i,
519         dumpRR(u)
520     for i in range(arcount):
521         print 'ADDITIONAL RECORD %d:' % i,
522         dumpRR(u)
523
524 class DnsResult:
525
526     def __init__(self,u,args):
527         self.header={}
528         self.questions=[]
529         self.answers=[]
530         self.authority=[]
531         self.additional=[]
532         self.args=args
533         self.storeM(u)
534
535     def show(self):
536         import time
537         print '; <<>> PDG.py 1.0 <<>> %s %s'%(self.args['name'],
538             self.args['qtype'])
539         opt=""
540         if self.args['rd']:
541             opt=opt+'recurs '
542         h=self.header
543         print ';; options: '+opt
544         print ';; got answer:'
545         print ';; ->>HEADER<<- opcode %s, status %s, id %d'%(
546             h['opcode'],h['status'],h['id'])
547         flags=filter(lambda x,h=h:h[x],('qr','aa','rd','ra','tc'))
548         print ';; flags: %s; Ques: %d, Ans: %d, Auth: %d, Addit: %d'%(
549             string.join(flags),h['qdcount'],h['ancount'],h['nscount'],
550             h['arcount'])
551         print ';; QUESTIONS:'
552         for q in self.questions:
553             print ';;      %s, type = %s, class = %s'%(q['qname'],q['qtypestr'],
554                 q['qclassstr'])
555         print
556         print ';; ANSWERS:'
557         for a in self.answers:
558             print '%-20s    %-6s  %-6s  %s'%(a['name'],`a['ttl']`,a['typename'],
559                 a['data'])
560         print
561         print ';; AUTHORITY RECORDS:'
562         for a in self.authority:
563             print '%-20s    %-6s  %-6s  %s'%(a['name'],`a['ttl']`,a['typename'],
564                 a['data'])
565         print
566         print ';; ADDITIONAL RECORDS:'
567         for a in self.additional:
568             print '%-20s    %-6s  %-6s  %s'%(a['name'],`a['ttl']`,a['typename'],
569                 a['data'])
570         print
571         if self.args.has_key('elapsed'):
572             print ';; Total query time: %d msec'%self.args['elapsed']
573         print ';; To SERVER: %s'%(self.args['server'])
574         print ';; WHEN: %s'%time.ctime(time.time())
575
576     def storeM(self,u):
577         (self.header['id'], self.header['qr'], self.header['opcode'],
578           self.header['aa'], self.header['tc'], self.header['rd'],
579           self.header['ra'], self.header['z'], self.header['rcode'],
580           self.header['qdcount'], self.header['ancount'],
581           self.header['nscount'], self.header['arcount']) = u.getHeader()
582         self.header['opcodestr']=Opcode.opcodestr(self.header['opcode'])
583         self.header['status']=Status.statusstr(self.header['rcode'])
584         for i in range(self.header['qdcount']):
585             #print 'QUESTION %d:' % i,
586             self.questions.append(self.storeQ(u))
587         for i in range(self.header['ancount']):
588             #print 'ANSWER %d:' % i,
589             self.answers.append(self.storeRR(u))
590         for i in range(self.header['nscount']):
591             #print 'AUTHORITY RECORD %d:' % i,
592             self.authority.append(self.storeRR(u))
593         for i in range(self.header['arcount']):
594             #print 'ADDITIONAL RECORD %d:' % i,
595             self.additional.append(self.storeRR(u))
596
597     def storeQ(self,u):
598         q={}
599         q['qname'], q['qtype'], q['qclass'] = u.getQuestion()
600         q['qtypestr']=Type.typestr(q['qtype'])
601         q['qclassstr']=Class.classstr(q['qclass'])
602         return q
603
604     def storeRR(self,u):
605         r={}
606         r['name'],r['type'],r['class'],r['ttl'],r['rdlength'] = u.getRRheader()
607         r['typename'] = Type.typestr(r['type'])
608         r['classstr'] = Class.classstr(r['class'])
609         #print 'name=%s, type=%d(%s), class=%d(%s), ttl=%d' \
610         #      % (name,
611         #        type, typename,
612         #        klass, Class.classstr(class),
613         #        ttl)
614         mname = 'get%sdata' % r['typename']
615         if hasattr(u, mname):
616             r['data']=getattr(u, mname)()
617         else:
618             r['data']=u.getbytes(r['rdlength'])
619         return r
620
621 def dumpQ(u):
622     qname, qtype, qclass = u.getQuestion()
623     print 'qname=%s, qtype=%d(%s), qclass=%d(%s)' \
624               % (qname,
625                  qtype, Type.typestr(qtype),
626                  qclass, Class.classstr(qclass))
627
628 def dumpRR(u):
629     name, type, klass, ttl, rdlength = u.getRRheader()
630     typename = Type.typestr(type)
631     print 'name=%s, type=%d(%s), class=%d(%s), ttl=%d' \
632               % (name,
633                  type, typename,
634                  klass, Class.classstr(klass),
635                  ttl)
636     mname = 'get%sdata' % typename
637     if hasattr(u, mname):
638         print '  formatted rdata:', getattr(u, mname)()
639     else:
640         print '  binary rdata:', u.getbytes(rdlength)
641
642 if __name__ == "__main__":
643     testpacker()
644 #
645 # $Log: Lib.py,v $
646 # Revision 1.11.2.8  2011/03/16 20:06:39  customdesigned
647 # Refer to explicit LICENSE file.
648 #
649 # Revision 1.11.2.7  2009/06/09 18:39:06  customdesigned
650 # Built-in SPF support
651 #
652 # Revision 1.11.2.6  2008/10/15 22:34:06  customdesigned
653 # Default to idna encoding.
654 #
655 # Revision 1.11.2.5  2008/09/17 17:35:14  customdesigned
656 # Use 7-bit ascii encoding, because case folding needs to be disabled
657 # before utf8 is safe to use, even experimentally.
658 #
659 # Revision 1.11.2.4  2008/09/17 16:09:53  customdesigned
660 # Encode unicode labels as UTF-8
661 #
662 # Revision 1.11.2.3  2007/05/22 20:27:40  customdesigned
663 # Fix unpacker underflow.
664 #
665 # Revision 1.11.2.2  2007/05/22 20:25:53  customdesigned
666 # Use socket.inetntoa,inetaton.
667 #
668 # Revision 1.11.2.1  2007/05/22 20:20:39  customdesigned
669 # Mark utf-8 encoding
670 #
671 # Revision 1.11  2002/03/19 13:05:02  anthonybaxter
672 # converted to class based exceptions (there goes the python1.4 compatibility :)
673 #
674 # removed a quite gross use of 'eval()'.
675 #
676 # Revision 1.10  2002/03/19 12:41:33  anthonybaxter
677 # tabnannied and reindented everything. 4 space indent, no tabs.
678 # yay.
679 #
680 # Revision 1.9  2002/03/19 10:30:33  anthonybaxter
681 # first round of major bits and pieces. The major stuff here (summarised
682 # from my local, off-net CVS server :/ this will cause some oddities with
683 # the
684 #
685 # tests/testPackers.py:
686 #   a large slab of unit tests for the packer and unpacker code in DNS.Lib
687 #
688 # DNS/Lib.py:
689 #   placeholder for addSRV.
690 #   added 'klass' to addA, make it the same as the other A* records.
691 #   made addTXT check for being passed a string, turn it into a length 1 list.
692 #   explicitly check for adding a string of length > 255 (prohibited).
693 #   a bunch of cleanups from a first pass with pychecker
694 #   new code for pack/unpack. the bitwise stuff uses struct, for a smallish
695 #     (disappointly small, actually) improvement, while addr2bin is much
696 #     much faster now.
697 #
698 # DNS/Base.py:
699 #   added DiscoverNameServers. This automatically does the right thing
700 #     on unix/ win32. No idea how MacOS handles this.  *sigh*
701 #     Incompatible change: Don't use ParseResolvConf on non-unix, use this
702 #     function, instead!
703 #   a bunch of cleanups from a first pass with pychecker
704 #
705 # Revision 1.8  2001/08/09 09:08:55  anthonybaxter
706 # added identifying header to top of each file
707 #
708 # Revision 1.7  2001/07/19 07:50:44  anthony
709 # Added SRV (RFC 2782) support. Code from Michael Ströder.
710 #
711 # Revision 1.6  2001/07/19 07:39:18  anthony
712 # 'type' -> 'rrtype' in getRRheader(). Fix from Michael Ströder.
713 #
714 # Revision 1.5  2001/07/19 07:34:19  anthony
715 # oops. glitch in storeRR (fixed now).
716 # Reported by Bastian Kleineidam and by greg lin.
717 #
718 # Revision 1.4  2001/07/19 07:16:42  anthony
719 # Changed (opcode&0xF)<<11 to (opcode*0xF)<<11.
720 # Patch from Timothy J. Miller.
721 #
722 # Revision 1.3  2001/07/19 06:57:07  anthony
723 # cvs keywords added
724 #
725 #