Initial import
[ldd.git] / ldd / rec.py
1 #    ldd - DNS implementation in Python
2 #    Copyright (C) 2006 Fredrik Tolf <fredrik@dolda2000.com>
3 #
4 #    This program is free software; you can redistribute it and/or modify
5 #    it under the terms of the GNU General Public License as published by
6 #    the Free Software Foundation; either version 2 of the License, or
7 #    (at your option) any later version.
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program; if not, write to the Free Software
16 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 import socket
19 import struct
20
21 import proto
22 import dn
23
24 rtypes = []
25
26 def addrtype(id, name, syntax):
27     rtypes.append((id, name, syntax))
28
29 def rtypebyid(id):
30     for rtype in rtypes:
31         if rtype[0] == id:
32             return rtype
33     return None
34
35 def rtypebyname(name):
36     for rtype in rtypes:
37         if rtype[1] == name.upper():
38             return rtype[0]
39     return None
40
41 class error(Exception):
42     def __init__(self, text):
43         self.text = text
44
45     def __str__(self):
46         return self.text
47
48 class malformedrr(Exception):
49     def __init__(self, text):
50         self.text = text
51
52     def __str__(self):
53         return self.text
54
55 class rrhead:
56     def __init__(self, name = None, rtype = None, rclass = None):
57         if rclass is None: rclass = CLASSIN
58         if type(name) == str:
59             self.name = dn.fromstring(name)
60         else:
61             self.name = name
62         if type(rtype) == str:
63             self.rtype = rtypebyname(rtype)
64             if self.rtype is None:
65                 raise error("no such rtype " + rtype)
66         else:
67             self.rtype = rtype
68         self.rclass = rclass
69
70     def encode(self, names, offset):
71         ret, names = proto.encodename(self.name, names, offset)
72         ret += struct.pack(">HH", self.rtype, self.rclass)
73         return ret, names
74
75     def __eq__(self, other):
76         return self.name == other.name and self.rtype == other.rtype
77
78     def __str__(self):
79         rtype = rtypebyid(self.rtype)
80         if rtype is None:
81             return "%02x RRhead %s" % (self.rtype, self.name)
82         else:
83             return "%s RRhead %s" % (rtype[1], self.name)
84
85     def istype(self, rtype):
86         if type(rtype) == str:
87             rtype = rtypebyname(rtype)
88         return self.rtype == rtype
89         
90     def decode(self, packet, offset):
91         name, offset = proto.decodename(packet, offset)
92         rtype, rclass = struct.unpack(">HH", packet[offset:offset + struct.calcsize(">HH")])
93         offset += struct.calcsize(">HH")
94         ret = rrhead(name, rtype, rclass)
95         return ret, offset
96     decode = classmethod(decode)
97
98 class rrdata:
99     def __init__(self, rtype, *args):
100         if type(rtype) == tuple and type(args[0]) == dict:
101             self.rtype = rtype
102             self.rdata = args[0]
103             return
104         
105         if type(rtype) == str:
106             self.rtype = rtypebyname(rtype)
107             if self.rtype is None:
108                 raise error("no such rtype " + rtype)
109         else:
110             self.rtype = rtype
111         rtid = self.rtype
112         self.rtype = rtypebyid(rtid)
113         if self.rtype is None:
114             raise error("no such rtype " + rtid)
115         self.rdata = {}
116         for i, e in enumerate(self.rtype[2]):
117             d = self.convdata(e[0], args[i])
118             self.rdata[e[1]] = d
119
120     def __eq__(self, other):
121         return(self.rdata == other.rdata)
122
123     def __str__(self):
124         ret = "{"
125         first = True
126         for e in self.rtype[2]:
127             if not first:
128                 ret += ", "
129             first = False
130             ret += e[1] + ": "
131             d = self.rdata[e[1]]
132             if e[0] == "4":
133                 ret += socket.inet_ntop(socket.AF_INET, d)
134             elif e[0] == "6":
135                 ret += socket.inet_ntop(socket.AF_INET6, d)
136             elif e[0] == "s":
137                 ret += '"' + d + '"'
138             else:
139                 ret += str(d)
140         ret += "}"
141         return ret
142
143     def istype(self, rtype):
144         if type(rtype) == str:
145             rtype = rtypebyname(rtype)
146         return self.rtype[0] == rtype
147         
148     def convdata(self, dtype, data):
149         if dtype == "4":
150             if type(data) != str:
151                 raise error("IPv4 address must be a string")
152             if len(data) == 4:
153                 d = data
154             else:
155                 d = socket.inet_pton(socket.AF_INET, data)
156         if dtype == "6":
157             if type(data) != str:
158                 raise error("IPv6 address must be a string")
159             if len(data) == 16 and data.find(":") == -1:
160                 d = data
161             else:
162                 d = socket.inet_pton(socket.AF_INET6, data)
163         if dtype == "d":
164             if type(data) == str:
165                 d = dn.fromstring(data)
166             elif isinstance(data, dn.domainname):
167                 d = data
168             else:
169                 raise error("Domain name must be either proper or string")
170         if dtype == "s":
171             d = str(data)
172         if dtype == "i":
173             d = int(data)
174         return d
175     
176     def __iter__(self):
177         return iter(self.rdata)
178     
179     def __getitem__(self, i):
180         return self.rdata[i]
181
182     def __setitem__(self, i, v):
183         for e in self.rtype[2]:
184             if e[1] == i:
185                 break
186         else:
187             raise error("No such data for " + self.rtype[1] + " record: " + str(i))
188         self.rdata[i] = self.convdata(e[0], v)
189
190     def encode(self, names, offset):
191         ret = ""
192         for e in self.rtype[2]:
193             d = self.rdata[e[1]]
194             if e[2] == "strc":
195                 ret += d
196                 offset += len(d)
197             if e[2] == "cmdn":
198                 buf, names = proto.encodename(d, names, offset)
199                 ret += buf
200                 offset += len(buf)
201             if e[2] == "lstr":
202                 ret += chr(len(d)) + d
203                 offset += 1 + len(d)
204             if e[2] == "llstr":
205                 ret += struct.pack(">H", len(d)) + d
206                 offset += struct.calcsize(">H") + len(d)
207             if e[2] == "short":
208                 ret += struct.pack(">H", d)
209                 offset += struct.calcsize(">H")
210             if e[2] == "long":
211                 ret += struct.pack(">L", d)
212                 offset += struct.calcsize(">L")
213             if e[2] == "int6":
214                 ret += struct.pack(">Q", d)[-6:]
215                 offset += 6
216         return ret, names
217
218     def decode(self, rtid, packet, offset, dlen):
219         rtype = rtypebyid(rtid)
220         origoff = offset
221         rdata = {}
222         if rtype is None:
223             rtype = (rtid, "Unknown", [("s", "unknown", "strc", dlen)])
224         for e in rtype[2]:
225             if e[2] == "strc":
226                 d = packet[offset:offset + e[3]]
227                 offset += e[3]
228             if e[2] == "cmdn":
229                 d, offset = proto.decodename(packet, offset)
230             if e[2] == "lstr":
231                 dl = ord(packet[offset])
232                 offset += 1
233                 d = packet[offset:offset + dl]
234                 offset += dl
235             if e[2] == "llstr":
236                 (dl,) = struct.unpack(">H", packet[offset:offset + struct.calcsize(">H")])
237                 offset += struct.calcsize(">H")
238                 d = packet[offset:offset + dl]
239                 offset += dl
240             if e[2] == "short":
241                 (d,) = struct.unpack(">H", packet[offset:offset + struct.calcsize(">H")])
242                 offset += struct.calcsize(">H")
243             if e[2] == "long":
244                 (d,) = struct.unpack(">L", packet[offset:offset + struct.calcsize(">L")])
245                 offset += struct.calcsize(">L")
246             if e[2] == "int6":
247                 (d,) = struct.unpack(">Q", ("\0" * (struct.calcsize(">Q") - 6)) + packet[offset:offset + 6])
248                 offset += 6
249             rdata[e[1]] = d
250         if origoff + dlen != offset:
251             raise malformedrr(rtype[1] + " RR data length mismatch")
252         return rrdata(rtype, rdata)
253     decode = classmethod(decode)
254
255 class rr:
256     def __init__(self, head, ttl, data):
257         if type(head) == tuple:
258             self.head = rrhead(*head)
259         else:
260             self.head = head
261         self.ttl = ttl
262         self.data = data
263         self.flags = set()
264
265     def setflags(self, flags):
266         self.flags |= set(flags)
267
268     def clrflags(self, flags):
269         self.flags -= set(flags)
270     
271     def encode(self, names, offset):
272         ret, names = self.head.encode(names, offset)
273         if self.data is None:
274             data = ""
275         else:
276             data, names = self.data.encode(names, offset + len(ret) + struct.calcsize(">LH"))
277         ret += struct.pack(">LH", self.ttl, len(data))
278         ret += data
279         return ret, names
280
281     def __eq__(self, other):
282         return self.head == other.head and self.ttl == other.ttl and self.data == other.data
283
284     def __str__(self):
285         rtype = rtypebyid(self.head.rtype)
286         if rtype is None:
287             ret = "%02x" % self.head.rtype
288         else:
289             ret = rtype[1]
290         ret += " RR %s, TTL=%i: %s" % (self.head.name, self.ttl, self.data)
291         if len(self.flags) > 0:
292             ret += " (Flags:"
293             for f in self.flags:
294                 ret += " " + f
295             ret += ")"
296         return ret
297     
298     def decode(self, packet, offset):
299         head, offset = rrhead.decode(packet, offset)
300         ttl, dlen = struct.unpack(">LH", packet[offset:offset + struct.calcsize(">LH")])
301         offset += struct.calcsize(">LH")
302         if dlen == 0:
303             data = None
304         else:
305             data = rrdata.decode(head.rtype, packet, offset, dlen)
306             offset += dlen
307         return rr(head, ttl, data), offset
308     decode = classmethod(decode)
309
310 addrtype(0x01, "A", [("4", "address", "strc", 4)])
311 addrtype(0x02, "NS", [("d", "nsname", "cmdn")])
312 addrtype(0x05, "CNAME", [("d", "priname", "cmdn")])
313 addrtype(0x06, "SOA", [("d", "priserv", "cmdn"),
314                        ("d", "mailbox", "cmdn"),
315                        ("i", "serial", "long"),
316                        ("i", "refresh", "long"),
317                        ("i", "retry", "long"),
318                        ("i", "expire", "long"),
319                        ("i", "minttl", "long")])
320 addrtype(0x0c, "PTR", [("d", "target", "cmdn")])
321 addrtype(0x0f, "MX", [("i", "prio", "short"),
322                       ("d", "target", "cmdn")])
323 addrtype(0x10, "TXT", [("s", "rrtext", "lstr")])
324 addrtype(0x1c, "AAAA", [("6", "address", "strc", 16)])
325 addrtype(0x21, "SRV", [("i", "prio", "short"),
326                        ("i", "weight", "short"),
327                        ("i", "port", "short"),
328                        ("d", "target", "cmdn")])
329 addrtype(0xfa, "TSIG", [("d", "algo", "cmdn"),
330                         ("i", "stime", "int6"),
331                         ("i", "fudge", "short"),
332                         ("s", "mac", "llstr"),
333                         ("i", "orgid", "short"),
334                         ("i", "err", "short"),
335                         ("s", "other", "llstr")])
336
337 CLASSIN = 1
338 CLASSCS = 2
339 CLASSCH = 3
340 CLASSHS = 4
341 CLASSNONE = 254
342 CLASSANY = 255