399677e984d697631d785802da58c2711751bb93
[kai/samba-autobuild/.git] / lib / dnspython / dns / rdata.py
1 # Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
2 #
3 # Permission to use, copy, modify, and distribute this software and its
4 # documentation for any purpose with or without fee is hereby granted,
5 # provided that the above copyright notice and this permission notice
6 # appear in all copies.
7 #
8 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16 """DNS rdata.
17
18 @var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
19 the module which implements that type.
20 @type _rdata_modules: dict
21 @var _module_prefix: The prefix to use when forming modules names.  The
22 default is 'dns.rdtypes'.  Changing this value will break the library.
23 @type _module_prefix: string
24 @var _hex_chunk: At most this many octets that will be represented in each
25 chunk of hexstring that _hexify() produces before whitespace occurs.
26 @type _hex_chunk: int"""
27
28 import cStringIO
29
30 import dns.exception
31 import dns.name
32 import dns.rdataclass
33 import dns.rdatatype
34 import dns.tokenizer
35
36 _hex_chunksize = 32
37
38 def _hexify(data, chunksize=None):
39     """Convert a binary string into its hex encoding, broken up into chunks
40     of I{chunksize} characters separated by a space.
41
42     @param data: the binary string
43     @type data: string
44     @param chunksize: the chunk size.  Default is L{dns.rdata._hex_chunksize}
45     @rtype: string
46     """
47
48     if chunksize is None:
49         chunksize = _hex_chunksize
50     hex = data.encode('hex_codec')
51     l = len(hex)
52     if l > chunksize:
53         chunks = []
54         i = 0
55         while i < l:
56             chunks.append(hex[i : i + chunksize])
57             i += chunksize
58         hex = ' '.join(chunks)
59     return hex
60
61 _base64_chunksize = 32
62
63 def _base64ify(data, chunksize=None):
64     """Convert a binary string into its base64 encoding, broken up into chunks
65     of I{chunksize} characters separated by a space.
66
67     @param data: the binary string
68     @type data: string
69     @param chunksize: the chunk size.  Default is
70     L{dns.rdata._base64_chunksize}
71     @rtype: string
72     """
73
74     if chunksize is None:
75         chunksize = _base64_chunksize
76     b64 = data.encode('base64_codec')
77     b64 = b64.replace('\n', '')
78     l = len(b64)
79     if l > chunksize:
80         chunks = []
81         i = 0
82         while i < l:
83             chunks.append(b64[i : i + chunksize])
84             i += chunksize
85         b64 = ' '.join(chunks)
86     return b64
87
88 __escaped = {
89     '"' : True,
90     '\\' : True,
91     }
92
93 def _escapify(qstring):
94     """Escape the characters in a quoted string which need it.
95
96     @param qstring: the string
97     @type qstring: string
98     @returns: the escaped string
99     @rtype: string
100     """
101
102     text = ''
103     for c in qstring:
104         if c in __escaped:
105             text += '\\' + c
106         elif ord(c) >= 0x20 and ord(c) < 0x7F:
107             text += c
108         else:
109             text += '\\%03d' % ord(c)
110     return text
111
112 def _truncate_bitmap(what):
113     """Determine the index of greatest byte that isn't all zeros, and
114     return the bitmap that contains all the bytes less than that index.
115
116     @param what: a string of octets representing a bitmap.
117     @type what: string
118     @rtype: string
119     """
120
121     for i in xrange(len(what) - 1, -1, -1):
122         if what[i] != '\x00':
123             break
124     return ''.join(what[0 : i + 1])
125
126 class Rdata(object):
127     """Base class for all DNS rdata types.
128     """
129
130     __slots__ = ['rdclass', 'rdtype']
131
132     def __init__(self, rdclass, rdtype):
133         """Initialize an rdata.
134         @param rdclass: The rdata class
135         @type rdclass: int
136         @param rdtype: The rdata type
137         @type rdtype: int
138         """
139
140         self.rdclass = rdclass
141         self.rdtype = rdtype
142
143     def covers(self):
144         """DNS SIG/RRSIG rdatas apply to a specific type; this type is
145         returned by the covers() function.  If the rdata type is not
146         SIG or RRSIG, dns.rdatatype.NONE is returned.  This is useful when
147         creating rdatasets, allowing the rdataset to contain only RRSIGs
148         of a particular type, e.g. RRSIG(NS).
149         @rtype: int
150         """
151
152         return dns.rdatatype.NONE
153
154     def extended_rdatatype(self):
155         """Return a 32-bit type value, the least significant 16 bits of
156         which are the ordinary DNS type, and the upper 16 bits of which are
157         the "covered" type, if any.
158         @rtype: int
159         """
160
161         return self.covers() << 16 | self.rdtype
162
163     def to_text(self, origin=None, relativize=True, **kw):
164         """Convert an rdata to text format.
165         @rtype: string
166         """
167         raise NotImplementedError
168
169     def to_wire(self, file, compress = None, origin = None):
170         """Convert an rdata to wire format.
171         @rtype: string
172         """
173
174         raise NotImplementedError
175
176     def to_digestable(self, origin = None):
177         """Convert rdata to a format suitable for digesting in hashes.  This
178         is also the DNSSEC canonical form."""
179         f = cStringIO.StringIO()
180         self.to_wire(f, None, origin)
181         return f.getvalue()
182
183     def validate(self):
184         """Check that the current contents of the rdata's fields are
185         valid.  If you change an rdata by assigning to its fields,
186         it is a good idea to call validate() when you are done making
187         changes.
188         """
189         dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
190
191     def __repr__(self):
192         covers = self.covers()
193         if covers == dns.rdatatype.NONE:
194             ctext = ''
195         else:
196             ctext = '(' + dns.rdatatype.to_text(covers) + ')'
197         return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
198                dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
199                str(self) + '>'
200
201     def __str__(self):
202         return self.to_text()
203
204     def _cmp(self, other):
205         """Compare an rdata with another rdata of the same rdtype and
206         rdclass.  Return < 0 if self < other in the DNSSEC ordering,
207         0 if self == other, and > 0 if self > other.
208         """
209
210         raise NotImplementedError
211
212     def __eq__(self, other):
213         if not isinstance(other, Rdata):
214             return False
215         if self.rdclass != other.rdclass or \
216            self.rdtype != other.rdtype:
217             return False
218         return self._cmp(other) == 0
219
220     def __ne__(self, other):
221         if not isinstance(other, Rdata):
222             return True
223         if self.rdclass != other.rdclass or \
224            self.rdtype != other.rdtype:
225             return True
226         return self._cmp(other) != 0
227
228     def __lt__(self, other):
229         if not isinstance(other, Rdata) or \
230                self.rdclass != other.rdclass or \
231                self.rdtype != other.rdtype:
232             return NotImplemented
233         return self._cmp(other) < 0
234
235     def __le__(self, other):
236         if not isinstance(other, Rdata) or \
237                self.rdclass != other.rdclass or \
238                self.rdtype != other.rdtype:
239             return NotImplemented
240         return self._cmp(other) <= 0
241
242     def __ge__(self, other):
243         if not isinstance(other, Rdata) or \
244                self.rdclass != other.rdclass or \
245                self.rdtype != other.rdtype:
246             return NotImplemented
247         return self._cmp(other) >= 0
248
249     def __gt__(self, other):
250         if not isinstance(other, Rdata) or \
251                self.rdclass != other.rdclass or \
252                self.rdtype != other.rdtype:
253             return NotImplemented
254         return self._cmp(other) > 0
255
256     def __hash__(self):
257         return hash(self.to_digestable(dns.name.root))
258
259     def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
260         """Build an rdata object from text format.
261
262         @param rdclass: The rdata class
263         @type rdclass: int
264         @param rdtype: The rdata type
265         @type rdtype: int
266         @param tok: The tokenizer
267         @type tok: dns.tokenizer.Tokenizer
268         @param origin: The origin to use for relative names
269         @type origin: dns.name.Name
270         @param relativize: should names be relativized?
271         @type relativize: bool
272         @rtype: dns.rdata.Rdata instance
273         """
274
275         raise NotImplementedError
276
277     from_text = classmethod(from_text)
278
279     def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
280         """Build an rdata object from wire format
281
282         @param rdclass: The rdata class
283         @type rdclass: int
284         @param rdtype: The rdata type
285         @type rdtype: int
286         @param wire: The wire-format message
287         @type wire: string
288         @param current: The offet in wire of the beginning of the rdata.
289         @type current: int
290         @param rdlen: The length of the wire-format rdata
291         @type rdlen: int
292         @param origin: The origin to use for relative names
293         @type origin: dns.name.Name
294         @rtype: dns.rdata.Rdata instance
295         """
296
297         raise NotImplementedError
298
299     from_wire = classmethod(from_wire)
300
301     def choose_relativity(self, origin = None, relativize = True):
302         """Convert any domain names in the rdata to the specified
303         relativization.
304         """
305
306         pass
307
308
309 class GenericRdata(Rdata):
310     """Generate Rdata Class
311
312     This class is used for rdata types for which we have no better
313     implementation.  It implements the DNS "unknown RRs" scheme.
314     """
315
316     __slots__ = ['data']
317
318     def __init__(self, rdclass, rdtype, data):
319         super(GenericRdata, self).__init__(rdclass, rdtype)
320         self.data = data
321
322     def to_text(self, origin=None, relativize=True, **kw):
323         return r'\# %d ' % len(self.data) + _hexify(self.data)
324
325     def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
326         token = tok.get()
327         if not token.is_identifier() or token.value != '\#':
328             raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
329         length = tok.get_int()
330         chunks = []
331         while 1:
332             token = tok.get()
333             if token.is_eol_or_eof():
334                 break
335             chunks.append(token.value)
336         hex = ''.join(chunks)
337         data = hex.decode('hex_codec')
338         if len(data) != length:
339             raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
340         return cls(rdclass, rdtype, data)
341
342     from_text = classmethod(from_text)
343
344     def to_wire(self, file, compress = None, origin = None):
345         file.write(self.data)
346
347     def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
348         return cls(rdclass, rdtype, wire[current : current + rdlen])
349
350     from_wire = classmethod(from_wire)
351
352     def _cmp(self, other):
353         return cmp(self.data, other.data)
354
355 _rdata_modules = {}
356 _module_prefix = 'dns.rdtypes'
357
358 def get_rdata_class(rdclass, rdtype):
359
360     def import_module(name):
361         mod = __import__(name)
362         components = name.split('.')
363         for comp in components[1:]:
364             mod = getattr(mod, comp)
365         return mod
366
367     mod = _rdata_modules.get((rdclass, rdtype))
368     rdclass_text = dns.rdataclass.to_text(rdclass)
369     rdtype_text = dns.rdatatype.to_text(rdtype)
370     rdtype_text = rdtype_text.replace('-', '_')
371     if not mod:
372         mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
373         if not mod:
374             try:
375                 mod = import_module('.'.join([_module_prefix,
376                                               rdclass_text, rdtype_text]))
377                 _rdata_modules[(rdclass, rdtype)] = mod
378             except ImportError:
379                 try:
380                     mod = import_module('.'.join([_module_prefix,
381                                                   'ANY', rdtype_text]))
382                     _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
383                 except ImportError:
384                     mod = None
385     if mod:
386         cls = getattr(mod, rdtype_text)
387     else:
388         cls = GenericRdata
389     return cls
390
391 def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
392     """Build an rdata object from text format.
393
394     This function attempts to dynamically load a class which
395     implements the specified rdata class and type.  If there is no
396     class-and-type-specific implementation, the GenericRdata class
397     is used.
398
399     Once a class is chosen, its from_text() class method is called
400     with the parameters to this function.
401
402     @param rdclass: The rdata class
403     @type rdclass: int
404     @param rdtype: The rdata type
405     @type rdtype: int
406     @param tok: The tokenizer
407     @type tok: dns.tokenizer.Tokenizer
408     @param origin: The origin to use for relative names
409     @type origin: dns.name.Name
410     @param relativize: Should names be relativized?
411     @type relativize: bool
412     @rtype: dns.rdata.Rdata instance"""
413
414     if isinstance(tok, str):
415         tok = dns.tokenizer.Tokenizer(tok)
416     cls = get_rdata_class(rdclass, rdtype)
417     if cls != GenericRdata:
418         # peek at first token
419         token = tok.get()
420         tok.unget(token)
421         if token.is_identifier() and \
422            token.value == r'\#':
423             #
424             # Known type using the generic syntax.  Extract the
425             # wire form from the generic syntax, and then run
426             # from_wire on it.
427             #
428             rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
429                                            relativize)
430             return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
431                              origin)
432     return cls.from_text(rdclass, rdtype, tok, origin, relativize)
433
434 def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
435     """Build an rdata object from wire format
436
437     This function attempts to dynamically load a class which
438     implements the specified rdata class and type.  If there is no
439     class-and-type-specific implementation, the GenericRdata class
440     is used.
441
442     Once a class is chosen, its from_wire() class method is called
443     with the parameters to this function.
444
445     @param rdclass: The rdata class
446     @type rdclass: int
447     @param rdtype: The rdata type
448     @type rdtype: int
449     @param wire: The wire-format message
450     @type wire: string
451     @param current: The offet in wire of the beginning of the rdata.
452     @type current: int
453     @param rdlen: The length of the wire-format rdata
454     @type rdlen: int
455     @param origin: The origin to use for relative names
456     @type origin: dns.name.Name
457     @rtype: dns.rdata.Rdata instance"""
458
459     cls = get_rdata_class(rdclass, rdtype)
460     return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)