source: trunk/server/lib/dnspython/dns/rdata.py

Last change on this file was 745, checked in by Silvan Scherrer, 13 years ago

Samba Server: updated trunk to 3.6.0

File size: 15.1 KB
Line 
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
19the module which implements that type.
20@type _rdata_modules: dict
21@var _module_prefix: The prefix to use when forming modules names. The
22default 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
25chunk of hexstring that _hexify() produces before whitespace occurs.
26@type _hex_chunk: int"""
27
28import cStringIO
29
30import dns.exception
31import dns.name
32import dns.rdataclass
33import dns.rdatatype
34import dns.tokenizer
35
36_hex_chunksize = 32
37
38def _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
63def _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
93def _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
112def _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
126class 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
309class 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
358def 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
391def 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
434def 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)
Note: See TracBrowser for help on using the repository browser.