| 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 |