+# This file is part of python-rwhoisd
+#
+# Copyright (C) 2008 David E. Blacka
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import socket, re, struct
+
+# a simplified regex that just makes sure that the IPv6 address string
+# isn't obviously invalid.
+v6char_re = re.compile(r'[0-9a-f:]+(:\d+\.\d+\.\d+\.\d+)?', re.I)
+
+def v6str_to_addr(addrstr):
+ """Convert IPv6 addresses into its packed numerical value."""
+
+ # first make sure the address is made of valid IPv6 address
+ # characters.
+ if not v6char_re.match(addrstr):
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+ toks = addrstr.split(":")
+ blanks = toks.count('')
+
+ # convert our IPv4 section.
+ if '.' in toks[-1]:
+ packed_v4 = socket.inet_aton(toks[-1])
+ unpacked_v4 = [ "%x" % (x) for x in struct.unpack("!HH", packed_v4) ]
+ toks[-1:] = unpacked_v4
+
+ if len(toks) > 8 or blanks > 3:
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+ # three blanks must be ::
+ if blanks == 3:
+ if addrstr != "::":
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+ return '\x00' * 16;
+
+ # convert the tokens into a regular array of 8 things by inserting
+ # zero strings for the elided section.
+
+ # two blanks must be ::blah or blah::
+ if blanks == 2:
+ z = ['0'] * (8 - len(toks) + 2)
+ if addrstr.startswith("::"):
+ toks[:2] = z
+ elif addrstr.endswith("::"):
+ toks[-2:] = z
+ else:
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+ # one blank is the blah::blah
+ elif blanks == 1:
+ z = ['0'] * (8 - len(toks) + 1)
+ i = toks.index('')
+ toks[i:i+1] = z
+
+ if len(toks) != 8:
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+ toks = [ int(t, 16) for t in toks ]
+ for t in toks:
+ if t & 0xFFFF != t:
+ raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+ return struct.pack("!8H", *toks)
+
+def v6addr_to_str(addr):
+ """Convert a packed numerical IPv6 address into a string. This
+ routine doesn't (yet) create an elided section."""
+
+ if len(addr) != 16:
+ raise socket.error("incorrect address length: %s (should be 16)" %
+ (len(addr)))
+ nums = [ x for x in struct.unpack("!8H", addr)]
+
+ # elding support mostly cobbled from the glibc version of
+ # inet_ntop
+
+ # look for the longest string of zeros.
+ cur_base = best_base = cur_len = best_len = -1
+
+ for i in range(8):
+ if nums[i] == 0:
+ if cur_base == -1:
+ cur_base, cur_len = i, 1
+ else:
+ cur_len += 1
+ else:
+ if cur_base != -1:
+ if best_base == -1 or cur_len > best_len:
+ best_base, best_len = cur_base, cur_len
+ cur_base = -1
+
+ if cur_base != -1:
+ if best_base == -1 or cur_len > best_len:
+ best_base, best_len = cur_base, cur_len
+
+ # if we have a valid string of zeros, replace them with the token.
+ if best_base != -1 and best_len > 1:
+ nums[best_base:best_base + best_len] = [':']
+
+ if nums[0] == ':':
+ nums.insert(0, ':')
+ if nums[-1] == ':':
+ nums.append(':')
+
+ def n_to_str(n):
+ if n == ':':
+ return ''
+ return "%x" % (n)
+ strs = [ n_to_str(x) for x in nums ]
+ return ":".join(strs)
+
+
+def inet_pton(af, ip):
+ if af == socket.AF_INET:
+ return socket.inet_aton(ip)
+ if af == socket.AF_INET6:
+ return v6str_to_addr(ip)
+ raise socket.error("Address family not supported by protocol")
+
+def inet_ntop(af, packed_ip):
+ if af == socket.AF_INET:
+ return socket.inet.ntoa(packed_ip)
+ if af == socket.AF_INET6:
+ return v6addr_to_str(packed_ip)
+ raise socket.error("Address family not supported by protocol")
+
+
+try:
+ socket.inet_pton(socket.AF_INET6, "::1")
+except (AttributeError, NameError, socket.error):
+ socket.inet_pton = inet_pton
+ socket.inet_ntop = inet_ntop
+ socket.AF_INET6 = 'AF_INET6'
+
+
+# test driver
+if __name__ == "__main__":
+
+ def try_good_addr(addr):
+ try:
+ a = v6str_to_addr(addr)
+ b = v6addr_to_str(a)
+ except socket.error, e:
+ print e
+ else:
+ print "%s => %s" % (addr, b)
+
+ try_good_addr("::");
+ try_good_addr("::7");
+ try_good_addr("f::");
+ try_good_addr("ab:0:0:c:0:0:0:d")
+ try_good_addr("ab:0:0:0:c:0:0:d")
+ try_good_addr("1:2:3:4:5:6:7:8")
+ try_good_addr("1:2:3::7:8")
+ try_good_addr("2001:3c09:102::23:af")
+ try_good_addr("::ffff:1.2.3.4")
+ try_good_addr("1:2:3:4:5:6:7:4.3.2.1")
+
+ def try_bad_addr(addr):
+ try:
+ a = v6str_to_addr(addr)
+ except socket.error, e:
+ print e
+ else:
+ print "addr was valid!", addr
+
+ # things that shouldn't parse
+ try_bad_addr(":")
+ try_bad_addr(":::")
+ try_bad_addr("1::2::3")
+ try_bad_addr("::3::")
+ try_bad_addr("::1.2.3")
+ try_bad_addr("12345::1")