chiark / gitweb /
Import release 0.1.3 v0.1.3
authorStephen Early <steve@greenend.org.uk>
Wed, 10 Oct 2001 15:11:00 +0000 (16:11 +0100)
committerStephen Early <steve@greenend.org.uk>
Wed, 18 May 2011 12:31:27 +0000 (13:31 +0100)
16 files changed:
CREDITS
Makefile.in
NOTES
README
TODO
conffile.c
ipaddr.py [new file with mode: 0644]
make-secnet-sites.py [new file with mode: 0755]
md5.c
modules.c
netlink.c
netlink.h
rsa.c
secnet.h
sha1.c [new file with mode: 0644]
site.c

diff --git a/CREDITS b/CREDITS
index 860e29913d60743294270a0de664647b90db1a5b..484c8ef5e0fa126afdff32e39bda42a9aa07a7d3 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -1,3 +1,6 @@
 Stephen Early <steve@greenend.org.uk> - original author
 Ross Anderson, Eli Biham, Lars Knudsen - serpent
 Colin Plumb, Ian Jackson - MD5 implementation
+Steve Reid <sreid@sea-to-sky.net>, James H. Brown <jbrown@burgoyne.com> - 
+  SHA1 implementation
+Cendio Systems AB - ipaddr.py
index d025e3a6daa456af4c69f0d9fe4fcabfc606bf55..a3ad397ecfe166ad40ce4bf8917aa9191bedfbd6 100644 (file)
@@ -18,7 +18,7 @@
 .PHONY:        all clean realclean dist install
 
 PACKAGE:=secnet
-VERSION:=0.1.2
+VERSION:=0.1.3
 
 @SET_MAKE@
 
@@ -45,19 +45,19 @@ TARGETS:=secnet
 
 OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \
        resolver.o random.o udp.o site.o transform.o netlink.o rsa.o dh.o \
-       serpent.o md5.o version.o tun.o slip.o
+       serpent.o md5.o version.o tun.o slip.o sha1.o
 
 DISTFILES:=COPYING CREDITS INSTALL Makefile.in NOTES README TODO \
        conffile.c conffile.fl conffile.h conffile.y \
        conffile_internal.h config.h.bot \
        config.h.in config.h.top configure \
        configure.in debian dh.c \
-       example-sites-file example.conf \
-       install.sh linux md5.c md5.h \
+       example-sites-file example.conf make-secnet-sites.py \
+       install.sh ipaddr.py linux md5.c md5.h \
        modules.c modules.h netlink.c netlink.h \
        random.c resolver.c rsa.c \
        secnet.c secnet.h serpent.c serpent.h serpentsboxes.h \
-       site.c slip.c stamp-h.in transform.c tun.c udp.c \
+       sha1.c site.c slip.c stamp-h.in transform.c tun.c udp.c \
        unaligned.h util.c util.h
 
 %.c:   %.y
diff --git a/NOTES b/NOTES
index 8e53ac71a5b44e4cb99140a8579180e467b7420f..ae65e4cecf58759b67b66278bda916a58decc5f6 100644 (file)
--- a/NOTES
+++ b/NOTES
@@ -4,7 +4,7 @@ Like the first (1995/6) version, we're tunnelling IP packets inside
 UDP packets. To defeat various restrictions which may be imposed on us
 by network providers (like the prohibition of incoming TCP
 connections) we're sticking with UDP for everything this time,
-including key setup.
+including key setup. This means we have to handle retries, etc.
 
 Other new features include being able to deal with subnets hidden
 behind changing 'real' IP addresses, and the ability to choose
@@ -12,6 +12,8 @@ algorithms and keys per pair of communicating sites.
 
 ** Configuration and structure
 
+[The original plan]
+
 The network is made up from a number of 'sites'. These are collections
 of machines with private IP addresses. The new secnet code runs on
 machines which have interfaces on the private site network and some
@@ -22,6 +24,95 @@ convenient for every gateway machine to use the same name for each
 tunnel endpoint, but this is not vital. Individual tunnels are
 identified by their two endpoint names.
 
+[The new plan]
+
+It appears that people want to be able to use secnet on mobile
+machines like laptops as well as to interconnect sites. In particular,
+they want to be able to use their laptop in three situations:
+
+1) connected to their internal LAN by a cable; no tunnel involved
+2) connected via wireless, using a tunnel to protect traffic
+3) connected to some other network, using a tunnel to access the
+internal LAN.
+
+They want the laptop to keep the same IP address all the time.
+
+Case (1) is simple.
+
+Case (2) requires that the laptop run a copy of secnet, and have a
+tunnel configured between it and the main internal LAN default
+gateway. secnet must support the concept of a 'soft' tunnel where it
+adds a route and causes the gateway to do proxy-ARP when the tunnel is
+up, and removes the route again when the tunnel is down.
+
+The usual prohibition of packets coming in from one tunnel and going
+out another must be relaxed in this case (in particular, the
+destination address of packets from these 'mobile station' tunnels may
+be another tunnel as well as the host).
+
+(Quick sanity check: if chiark's secnet address was in
+192.168.73.0/24, would this work properly? Yes, because there will be
+an explicit route to it, and proxy ARP will be done for it. Do we want
+packets from the chiark tunnel to be able to go out along other
+routes? No. So, spotting a 'local' address in a remote site's list of
+networks isn't sufficient to switch on routing for a site. We need an
+explicit option. NB packets may be routed if the source OR the
+destination is marked as allowing routing [otherwise packets couldn't
+get back from eg. chiark to a laptop at greenend]).
+
+** VPN-level configuration
+
+At a high level we just want to be able to indicate which groups of
+users can claim ownership of which ranges of IP addresses. Assuming
+these users (or their representatives) all have accounts on a single
+machine, we can automate the submission of keys and other information
+to make up a 'sites' file for the entire VPN.
+
+The distributed 'sites' file should be in a more restricted format
+than the secnet configuration file, to prevent attackers who manage to
+distribute bogus sites files from taking over their victim's machines.
+
+The distributed 'sites' file is read one line at a time. Each line
+consists of a keyword followed by other information. It defines a
+number of VPNs; within each VPN it defines a number of locations;
+within each location it defines a number of sites. These VPNs,
+locations and sites are turned into a secnet.conf file fragment using
+a script.
+
+Some keywords are valid at any 'level' of the distributed 'sites'
+file, indicating defaults.
+
+The keywords are:
+
+vpn n: we are now declaring information to do with VPN 'n'. Must come first.
+
+location n: we are now declaring information for location 'n'.
+
+site n: we are now declaring information for site 'n'.
+endsite: we're finished declaring information for the current site
+
+restrict-nets a b c ...: restrict the allowable 'networks' for the current
+  level to those in this list.
+end-definitions: prevent definition of further vpns and locations, and
+  modification of defaults at VPN level
+
+dh x y: the current VPN uses the specified group; x=modulus, y=generator
+
+hash x: which hash function to use. Valid options are 'md5' and 'sha1'.
+
+admin n: administrator email address for current level
+
+key-lifetime n
+setup-retries n
+setup-timeout n
+wait-time n
+renegotiate-time n
+
+address a b: a=dnsname, b=port
+networks a b c ...
+pubkey x y z: x=keylen, y=encryption key, z=modulus
+mobile: declare this to be a 'mobile' site
+
 ** Protocols
 
 *** Protocol environment:
diff --git a/README b/README
index 14b7ad3d874cf4eb2b1b00854f70da736c5edfe3..35041831e5e83b17a39eec71129b44fbcb05484c 100644 (file)
--- a/README
+++ b/README
@@ -145,16 +145,27 @@ XXX TODO
 Defines:
   adns (closure => resolver closure)
 
+adns: dict argument
+  config (string): optional, a resolv.conf for ADNS to use
+
 ** random
 
 Defines:
   randomsrc (closure => randomsrc closure)
 
+randomsrc: string[,bool]
+  arg1: filename of random source
+  arg2: if True then source is blocking
+
 ** udp
 
 Defines:
   udp (closure => comm closure)
 
+udp: dict argument
+  port (integer): UDP port to listen and send on
+  buffer (buffer closure): buffer for incoming packets
+
 ** util
 
 Defines:
@@ -166,6 +177,51 @@ Defines:
 Defines:
   site (closure => site closure)
 
+site: dict argument
+  local-name (string): this site's name for itself
+  name (string): the name of the site's peer
+  netlink (netlink closure)
+  comm (comm closure)
+  resolver (resolver closure)
+  random (randomsrc closure)
+  local-key (rsaprivkey closure)
+  address (string): optional, DNS name used to find our peer
+  port (integer): mandatory if 'address' is specified: the port used
+    to contact our peer
+  networks (string list): networks that our peer may claim traffic for
+  key (rsapubkey closure): our peer's public key
+  transform (transform closure): how to mangle packets sent between sites
+  dh (dh closure)
+  hash (hash closure)
+  key-lifetime (integer): max lifetime of a session key, in ms [one hour]
+  setup-retries (integer): max number of times to transmit a key negotiation
+    packet [5]
+  setup-timeout (integer): time between retransmissions of key negotiation
+    packets, in ms [1000]
+  wait-time (integer): after failed key setup, wait this long (in ms) before
+    allowing another attempt [20000]
+  renegotiate-time (integer): if we see traffic on the link after this time
+    then renegotiate another session key immediately [depends on key-lifetime]
+  keepalive (bool): if True then attempt always to keep a valid session key
+  log-events (string list): types of events to log for this site
+    unexpected: unexpected key setup packets (may be late retransmissions)
+    setup-init: start of attempt to setup a session key
+    setup-timeout: failure of attempt to setup a session key, through timeout
+    activate-key: activation of a new session key
+    timeout-key: deletion of current session key through age
+    security: anything potentially suspicious
+    state-change: steps in the key setup protocol
+    packet-drop: whenever we throw away an outgoing packet
+    dump-packets: every key setup packet we see
+    errors: failure of name resolution, internal errors
+    all: everything (too much!)
+  netlink-options (string list): options to pass to netlink device when
+    registering remote networks
+    soft: create 'soft' routes that go away when there's no key established
+      with the peer
+    allow-route: allow packets from our peer to be sent down other tunnels,
+      as well as to the host
+
 ** transform
 
 Defines:
@@ -173,11 +229,52 @@ Defines:
 
 ** netlink
 
+Defines:
+  null-netlink (closure => netlink closure)
+
+null-netlink: dict argument
+  name (string): name for netlink device, used in log messages
+  networks (string list): networks on the host side of the netlink device
+  exclude-remote-networks (string list): networks that may never be claimed
+    by any remote site using this netlink device
+  local-address (string): IP address of host's tunnel interface
+  secnet-address (string): IP address of this netlink device
+  mtu (integer): MTU of host's tunnel interface
+
+** slip
+
 Defines:
   userv-ipif (closure => netlink closure)
+
+userv-ipif: dict argument
+  userv-path (string): optional, where to find userv ["userv"]
+  service-user (string): optional, username for userv-ipif service ["root"]
+  service-name (string): optional, name of userv-ipif service ["ipif"]
+  buffer (buffer closure): buffer for assembly of host->secnet packets
+ plus generic netlink options, as for 'null-netlink'
+
+** tun
+
+Defines:
   tun (closure => netlink closure) [only on linux-2.4]
   tun-old (closure => netlink closure)
-  null-netlink (closure => netlink closure)
+
+tun: dict argument
+  device (string): optional, path of TUN/TAP device file ["/dev/net/tun"]
+  interface (string): optional, name of tunnel network interface
+  ifconfig-path (string): optional, path to ifconfig command
+  route-path (string): optional, path to route command
+  buffer (buffer closure): buffer for host->secnet packets
+ plus generic netlink options, as for 'null-netlink'
+
+tun-old: dict argument
+  device (string): optional, path of TUN/TAP device file ["/dev/tun*"]
+  interface (string): optional, name of tunnel network interface
+  interface-search (bool): optional, whether to search for a free tunnel
+    interface (True if 'device' not specified, otherwise False)
+  ifconfig-path (string): optional, path to ifconfig command
+  route-path (string): optional, path to route command
+ plus generic netlink options, as for 'null-netlink'
 
 ** rsa
 
@@ -185,12 +282,30 @@ Defines:
   rsa-private (closure => rsaprivkey closure)
   rsa-public (closure => rsapubkey closure)
 
+rsa-private: string[,bool]
+  arg1: filename of SSH private key file (version 1, no password)
+  arg2: whether to check that the key is usable [default True]
+
+rsa-public: string,string
+  arg1: encryption key (decimal)
+  arg2: modulus (decimal)
+
 ** dh
 
 Defines:
   diffie-hellman (closure => dh closure)
 
+diffie-hellman: string,string[,bool]
+  arg1: modulus (hex)
+  arg2: generator (hex)
+  arg3: whether to check that the modulus is prime [default True]
+
 ** md5
 
 Defines:
   md5 (hash closure)
+
+** sha1
+
+Defines:
+  sha1 (hash closure)
diff --git a/TODO b/TODO
index aea5e0d4bfc9bac0f5a066c65cd1935f0f3642f2..e9ae871bb7214e82427a9becfa17f3561288b4ed 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,43 +1,41 @@
-configure.in: done
-
 Makefile.in: autodep stuff
 
-conffile.c: done
-
 dh.c: change format to binary from decimal string (without introducing
 endianness problems)
 
-netlink.c: done
+netlink.c: investigate why 'default' routes don't appear to work
+(reported by JDA).
 
-slip.c: done. Detect failure of userv-ipif to start.
+slip.c: detect failure of userv-ipif to start.
 
 tun.c: jdamery reports tun-old code works on Linux-2.2.
-Unresolved problem with ioctl(TUNSETIFF) sometimes return EINVAL.
+Unresolved problem with ioctl(TUNSETIFF) sometimes returning EINVAL, seems
+to be related to early 2.4.x (x<=5) series kernels. 2.4.9 and above seem ok;
+2.4.[678] untested.
 
 random.c: test
 
-resolver.c: done
-
 rsa.c: check padding type, change format to binary from decimal string
 (without introducing endianness problems)
 
-secnet.c: done
-
 site.c: the site_incoming() routing could be implemented much more
 cleanly using a table. There's still quite a lot of redundancy in this
 file. Abandon key exchanges when a bad packet is received. Modify
 protocol to include version fields, as described in the NOTES file.
 
-transform.c: done. JDA reports endianness problems are fixed.
-
-udp.c: done
+transform.c: make generic
 
 util.c: sort out logging
 
+sha1.c: test
+
 General: separate the transforms in transform.c into multiple parts,
 which can then be combined in the configuration file.  Will allow the
 user to plug in different block ciphers, invent an authenticity-only
 mode, etc.
 
+Signal handling! Really just cope with SIGCHLD and SIGTERM. Possibly
+use SIGUSR1/2 for prodding things.
+
 Write scripts to generate the 'real' sites file from a less-expressive
 version that's more easily checked by external tools.
index 9e10efa2e3881c36db72afd9223074e72f3351d8..5bd735c75a77c051e4fb679f265f52c2bec30208 100644 (file)
@@ -551,10 +551,28 @@ list_t *list_new(void)
     return NULL;
 }
 
+list_t *list_copy(list_t *a)
+{
+    list_t *r, *i, *b, *l;
+
+    if (!a) return NULL;
+    l=NULL;
+    r=NULL;
+    for (i=a; i; i=i->next) {
+       b=safe_malloc(sizeof(*b),"list_copy");
+       if (l) l->next=b; else r=b;
+       l=b;
+       b->item=i->item;
+       b->next=NULL;
+    }
+    return r;
+}
+
 list_t *list_append_list(list_t *a, list_t *b)
 {
     list_t *i;
 
+    b=list_copy(b);
     if (!a) return b;
     for (i=a; i->next; i=i->next);
     i->next=b;
diff --git a/ipaddr.py b/ipaddr.py
new file mode 100644 (file)
index 0000000..83f5a17
--- /dev/null
+++ b/ipaddr.py
@@ -0,0 +1,1243 @@
+# ipaddr.py -- handle IP addresses and set of IP addresses.
+# Copyright (C) 1996-2000 Cendio Systems AB
+#
+# 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
+
+"""IP address manipulation.
+
+This module is useful if you need to manipulate IP addresses or sets
+of IP addresses.
+
+The main classes are:
+
+    ipaddr   -- a single IP address.
+    netmask  -- a netmask.
+    network  -- an IP address/netmask combination.  It is often, but
+               not always, better to use the ip_set class instead.
+    ip_set   -- a set of IP addresses, that may or may not be adjacent.
+
+So, what can you do with this module?  As a simple example of the kind
+of things this module can do, this code computes the set of all IP
+addresses except 127.0.0.0/8 and prints it, expressed as a union of
+network/netmask pairs.
+
+    import ipaddr
+
+    s = ipaddr.ip_set()
+    s.add_network(ipaddr.network('127.0.0.0', '255.0.0.0',
+                                 ipaddr.DEMAND_FILTER))
+    for nw in s.complement().as_list_of_networks():
+       print nw.ip_str() + '/' + nw.mask.netmask_bits_str
+
+Errors are reported by raising an exception from the following
+exception hierarcy:
+
+Exception       # The standard Python base exception class.
+ |
+ +-- BadType    # Only raised if the programmer makes an error.
+ +-- IpError    # Base class for errors that depend on the data.
+      |
+      +-- SetNotRepresentable
+      +-- BrokenIpAddress
+      |    |
+      |    +-- PartNegative
+      |    +-- PartOverflow
+      |
+      +-- BrokenNetmask
+      |    |
+      |    +-- NeedOneBit
+      |    +-- NeedMoreBits
+      |    +-- NeedLessBits
+      |
+      +-- BrokenNetwork
+           |
+           +-- EmptyIpAddress
+           +-- EmptyNetmask
+           +-- BrokenNetworkAddress
+           +-- NetworkAddressClash
+           +-- BroadcastAddressClash
+  
+BadType may be raised at any time if the programmer makes an error
+(such as passing a dictionary to a function that expects a string).
+SetNotRepresentable may be raised by ip_set.as_str_range().  All other
+exceptions are raised from the constructors and helper functions only.
+
+The following constants are present in this module:
+
+    DEMAND_NONE                See class network.
+    DEMAND_FILTER      See class network.
+    DEMAND_NETWORK     See class network.
+    DEMAND_INTERFACE   See class network.
+
+    hostmask           A netmask object with all 32 bits set.
+    complete_network   A network object representing all IP addresses.
+    complete_set       An ip_set object representing all IP addresses.
+    broadcast_network  A network object representing 255.255.255.255.
+    broadcast_set      An ip_set object representing 255.255.255.255.
+    
+The as_ipaddr function can be used when you have an object that you
+know are an ipaddr or network, and you want to get the ipaddr part.
+
+All the other functions in this module are internal helper functions,
+and they should not be used.
+
+The internal representation used for IP addresses is currently a long
+number.  That may change in the future, so where the internal
+representation is visible, you should do nothing with it except
+compare it to None.
+
+This module was developed by Cendio Systems AB for use in the Fuego
+Firewall.  Bug reports can be sent to Per Cederqvist <ceder@cendio.se>
+who is currently acting as maintainer for this module.
+
+Brief history:
+    1997-03-11      Module created, and used internally.
+    2000-03-09 1.0: First non-public beta release outside of Cendio Systems.
+    2000-03-17 1.1: First public release under the GNU GPL license.
+
+"""
+
+
+import copy
+import string
+import types
+
+# The error messages are marked with a call to this function, so that
+# they can easily be found and translated.
+def _(s):
+    return s
+
+# The exception hierarchy.
+class IpError(Exception):
+    """Base class for errors that are cause by errors in input data.
+    """
+    def __str__(self):
+       return self.format % self.args
+
+class SetNotRepresentable(IpError):
+    format = _("The set of IP addresses cannot be represented "
+              "as a single network range")
+
+class BrokenIpAddress(IpError):
+    format = _("Felaktigt IP-nummer")
+
+class PartNegative(BrokenIpAddress):
+    format = _("En komponent i IP-numret Ã¤r negativ")
+
+class PartOverflow(BrokenIpAddress):
+    format = _("En komponent i IP-numret Ã¤r större Ã¤n 255")
+
+class BrokenNetmask(IpError):
+    format = _("Felaktig nätmask")
+
+class NeedOneBit(BrokenNetmask):
+    format = _("Minst en bit mÃ¥ste vara ettställd")
+
+class NeedMoreBits(BrokenNetmask):
+    format = _("Minst %d bitar mÃ¥ste vara ettställda")
+
+class NeedLessBits(BrokenNetmask):
+    format = _("Högst %d bitar fÃ¥r vara ettställda")
+
+class BrokenNetwork(IpError):
+    """Base class for errors regarding network objects.
+    """
+
+class EmptyIpAddress(BrokenNetwork):
+    format = _("IP-nummer ej ifyllt")
+
+class EmptyNetmask(BrokenNetwork):
+    format = _("Nätmask ej ifylld")
+
+class BrokenNetworkAddress(BrokenNetwork):
+    format = _("Med denna nätmask Ã¤r %s ett otillÃ¥tet nätverksnummer; "
+              "menar du %s?")
+
+class NetworkAddressClash(BrokenNetwork):
+    format = _("Med denna nätmask krockar Fuegons adress med nätnumret")
+
+class BroadcastAddressClash(BrokenNetwork):
+    format = _("Med denna nätmask krockar Fuegons adress "
+              "med broadcastadressen")
+
+class BadType(Exception):
+    """An object of an unexpected type was passed to a function.
+    """
+    pass
+
+# These constants are used with netmasks and networks to specify what
+# the code expects.
+#
+#  DEMAND_NONE: netmask 0-32 (inclusive)
+#  DEMAND_FILTER: netmask 0-32, the host part must be all zeroes
+#  DEMAND_NETWORK: netmask 1-32, the host part must be all zeroes
+#  DEMAND_INTERFACE: netmask 1-30, the host part must *not* be all zeroes
+
+DEMAND_NONE = 1
+DEMAND_FILTER = 2
+DEMAND_NETWORK = 3
+DEMAND_INTERFACE = 4
+
+def bits_to_intrep(bits):
+    """Convert BITS to the internal representation.
+
+    BITS should be a number in the range 0-32 (inclusive).
+
+    """
+    return pow(2L, 32) - pow(2L, 32-bits)
+
+
+def intrep_with_bit_set(bit):
+    """Return an internal representation with bit BIT set.
+
+    BIT should be a number in the range 1-32, where bit 1 is the
+    leftmost.  Examples:
+
+      intrep_with_bit_set(1) --> the internal representation of 128.0.0.0
+      intrep_with_bit_set(32) --> the internal representation of 0.0.0.1
+    """
+    assert 0 < bit and bit <= 32
+
+    return pow(2L, 32-bit)
+
+
+__ONES = {0:0, 128:1, 192:2, 224:3,
+         240:4, 248:5, 252:6, 254:7}
+
+def tuple_to_bits(mask):
+    """Convert MASK to bits.
+
+    MASK should be a tuple of four integers in the range 0-255 (inclusive).
+
+    Raises BrokenNetmask if MASK is not a valid netmask.
+    """
+
+    if mask == None:
+       return None
+    else:
+       (a, b, c, d) = mask
+
+       if a == 255 and b == 255 and c == 255 and d == 255:
+           return 32
+
+       try:
+           if a == 255 and b == 255 and c == 255:
+               return 24 + __ONES[d]
+           elif a == 255 and b == 255  and d == 0:
+               return 16 + __ONES[c]
+           elif a == 255 and c == 0  and d == 0:
+               return 8 + __ONES[b]
+           elif b == 0 and c == 0  and d == 0:
+               return __ONES[a]
+       except KeyError:
+           pass
+
+       raise BrokenNetmask()
+
+
+def intrep_to_dotted_decimal(t):
+    """Convert T to dotted-decimal notation.
+
+    T should be the internal representation used py ipaddr.py.
+    """
+
+    return (str(int(t>>24)) + '.' + str(int((t>>16) & 255))
+           + '.' + str(int((t>>8) & 255)) + '.' + str(int(t & 255)))
+
+
+def as_ipaddr(nwip):
+    """Return the IP address object of NWIP.
+
+    NWIP may be an ipaddr object, which is returned unchanged,
+    or a network object, in which case the ipaddr part of it is
+    returned.
+    """
+
+    if isinstance(nwip, ipaddr):
+       return nwip
+    elif isinstance(nwip, network):
+       return nwip.ip
+    else:
+       raise BadType('Expected a network or ipaddr object', nwip)
+
+
+class ipaddr:
+    """Handle IP addresses.
+
+    Sample use:
+
+        ip1 = ipaddr('12.3.5.1')
+       ip2 = ipaddr([12, 3, 5, 1])
+       print ip1.ip_str()
+       >>> '12.3.5.1'
+       print ip1.intrep
+       >>> 201524481L
+       print ip2.ip_str()
+       >>> '12.3.5.1'
+       print ip2.intrep
+       >>> 201524481L
+
+    An ipaddr object can have two states: empty or good.
+    The status can be examined like this:
+
+       if ip.intrep == None:
+           handle_empty(m.user_input())
+       else:
+           handle_good(ip)
+
+    All other members should only be used in the good state.  The
+    value stored in the intrep member should only be compared against
+    None.  The type and value of it is an internal detail that may
+    change in the future.
+
+    """
+
+    def __init__(self, ip):
+       """Create an ipaddr from IP (a string, tuple or list).
+
+       The empty string or None may be given; it is handled as the
+       empty IP number.
+       """
+
+       if type(ip) == types.StringType:
+           self.__user_input = ip
+           ip = string.strip(ip)
+       else:
+           self.__user_input = None
+
+       # The empty IP number?
+
+       if ip == '' or ip == None:
+           self.__ip_str = ''
+           self.intrep = None
+           if ip == None:
+               self.__user_input = ''
+           return
+
+       if type(ip) == types.StringType:
+
+           # Convert a string.
+
+           try:
+               [a, b, c, d] = map(string.atoi, string.splitfields(ip, '.'))
+           except:
+               raise BrokenIpAddress()
+
+           if a < 0 or b < 0 or c < 0 or d < 0:
+               raise PartNegative()
+
+           if a > 255 or b > 255 or c > 255 or d > 255:
+               raise PartOverflow()
+
+           self.intrep = (long(a) << 24) + (b << 16) + (c << 8) + d
+
+       else:
+           assert type(ip) == types.LongType
+           self.intrep = ip
+
+       self.__ip_str = None
+
+    def ip_str(self):
+       if self.__ip_str == None:
+           self.__ip_str = intrep_to_dotted_decimal(self.intrep)
+       return self.__ip_str
+
+    def user_input(self):
+       if self.__user_input == None:
+           # This object was constructed from a tuple.  Generate a string.
+           self.__user_input = self.ip_str()
+       return self.__user_input
+
+    def compare(self, other):
+       """Compare this IP address with OTHER.
+
+       Returns -1, 0 or 1 if this IP address is less than, equal to,
+       or greater than OTHER (which should be an ipaddr object).
+       """
+       # FIXME: should we rename this __cmp__?  It needs to handle
+       # other types of the OTHER argument first.
+
+       if self.intrep == other.intrep:
+           return 0
+       if self.intrep < other.intrep:
+           return -1
+       else:
+           return 1
+
+    def __str__(self):
+        if self.intrep is None:
+            return "<ipaddr empty>"
+        else:
+            return "<ipaddr %s>" % self.ip_str()
+
+    def __repr__(self):
+       if self.intrep is None:
+           return "ipaddr.ipaddr('')"
+       else:
+           return "ipaddr.ipaddr('%s')" % self.ip_str()
+
+
+class netmask:
+    """Handle netmasks.
+
+    Sample use:
+
+       # Four ways to initialize a netmask.
+        nm1 = netmask('255.255.128.0', DEMAND_NONE)
+       nm2 = netmask([255, 255, 128, 0], DEMAND_NONE)
+       nm3 = netmask('17', DEMAND_NONE)
+       nm4 = netmask(17, DEMAND_NONE)
+       print nm1.netmask_str()
+       >>> '255.255.128.0'
+       print nm1.intrep
+       >>> (255, 255, 128, 0)
+       print nm1.netmask_bits
+       >>> 17
+       print nm1.netmask_bits_str
+       >>> '17'
+
+    A netmask can have two states: empty or good.  The state
+    can be examined like this:
+
+       if m.intrep == None:
+           handle_empty(m.user_input())
+       else:
+           handle_good(m)
+
+    All other members should be used only in the good state.
+
+    """
+
+    def __check_range(self, bits, minbits, maxbits):
+       if bits < minbits:
+           if minbits == 1:
+               raise NeedOneBit()
+           else:
+               raise NeedMoreBits(minbits)
+       elif bits > maxbits:
+           raise NeedLessBits(maxbits)
+
+
+    def __set_from_bits(self, bits, minbits, maxbits):
+       self.__check_range(bits, minbits, maxbits)
+       self.intrep = bits_to_intrep(bits)
+       self.netmask_bits = bits
+
+
+    def __set_from_tuple(self, tpl, minbits, maxbits):
+       bits = tuple_to_bits(tpl)
+       self.__check_range(bits, minbits, maxbits)
+       self.intrep = bits_to_intrep(bits)
+       self.netmask_bits = bits
+
+    DEMANDS = {DEMAND_NONE:(0,32),
+              DEMAND_FILTER:(0,32),
+              DEMAND_NETWORK:(1,32),
+              DEMAND_INTERFACE:(1,30)}
+
+    def __init__(self, mask, demand):
+       """Create a netmask from MASK (a string, tuple or number) and DEMAND.
+
+       The empty string or None may be given; it is handled as the
+       empty netmask.
+
+       See class network for a description of the DEMAND parameter.
+       """
+
+       (minbits, maxbits) = self.DEMANDS[demand]
+       self.demand = demand
+
+       if type(mask) == types.StringType:
+           self.__user_input = mask
+           mask = string.strip(mask)
+       else:
+           self.__user_input = None
+
+       if mask == '' or mask == None:
+
+           # Handle empty netmasks.
+
+           self.__netmask_str = ''
+           self.intrep = None
+           self.netmask_bits_str = ''
+           self.netmask_bits = None
+           if self.__user_input == None:
+               self.input = ''
+           return
+
+       # Decode the MASK argument and set self.netmask_bits
+       # and self.intrep.
+
+       if type(mask) == types.StringType:
+
+           # Is this a string containing a single number?
+           try:
+               bits = string.atoi(mask)
+           except (OverflowError, ValueError):
+               bits = None
+
+           if bits != None:
+
+               # This is a string containing a single number.
+
+               self.__set_from_bits(bits, minbits, maxbits)
+
+           else:
+
+               # Interpret the netmask as a dotted four-tuple.
+               try:
+                   [a, b, c, d] = map(string.atoi,
+                                      string.splitfields(mask, '.'))
+               except:
+                   raise BrokenNetmask()
+
+               self.__set_from_tuple((a, b, c, d), minbits, maxbits)
+
+       elif type(mask) == types.IntType:
+
+           # This is a number, representing the number of bits in the mask.
+
+           self.__set_from_bits(mask, minbits, maxbits)
+
+       else:
+
+           # This is a tuple or list.
+
+           if len(mask) != 4:
+               raise BadType('Wrong len of tuple/list')
+
+           (a, b, c, d) = (mask[0], mask[1], mask[2], mask[3])
+
+           self.__set_from_tuple((a, b, c, d), minbits, maxbits)
+
+       self.__netmask_str = None
+       self.netmask_bits_str = repr(self.netmask_bits)
+
+    def netmask_str(self):
+       if self.__netmask_str == None:
+           self.__netmask_str = intrep_to_dotted_decimal(self.intrep)
+       return self.__netmask_str
+
+    def user_input(self):
+       if self.__user_input == None:
+           # This object was constructed from a tuple or an integer.
+           self.__user_input = self.ip_str()
+       return self.__user_input
+
+    def __str__(self):
+        if self.intrep is None:
+            return "<netmask empty>"
+        else:
+            return "<netmask /%d>" % self.netmask_bits
+
+    def __repr__(self):
+        if self.intrep is None:
+            return "ipaddr.netmask('')"
+        else:
+            return "ipaddr.netmask(%d, %d)" % (self.netmask_bits, self.demand)
+
+
+hostmask = netmask(32, DEMAND_NONE)
+       
+
+class network:
+    """Designate a network or host.
+
+    The constructor takes three arguments: the IP number part, the
+    netmask part, and a demand parameter.  See class ipaddr and class
+    netmask for a description of the first two arguments.  The demand
+    argument can be one of the following constants:
+
+    DEMAND_NONE
+        No special demands.
+    DEMAND_FILTER
+        The host part must be all zeroes.
+    DEMAND_NETWORK
+        The netmask must be 1-32
+       The host part must be all zeroes.
+    DEMAND_INTERFACE
+        The netmask must be 1-30
+       The host part must *not* be all zeroes (the network address)
+        or all ones (the broadcast address).
+
+    The following members exist and are set by the constructor:
+
+      ip.user_input()          # a caching function
+      ip_str()                 # a caching function
+      ip.intrep
+      mask.user_input()                # a caching function
+      mask.netmask_str()       # a caching function
+      mask.intrep
+      mask.netmask_bits
+      mask.netmask_bits_str
+      network_str()            # a caching function
+      network_intrep
+      broadcast_str()          # a caching function
+      broadcast_intrep
+      host_part_str()          # a caching function
+      host_part_intrep
+
+    """
+
+    def __init__(self, ip, mask, demand):
+       self.ip = ipaddr(ip)
+       self.mask = netmask(mask, demand)
+
+       if self.ip.intrep == None:
+           raise EmptyIpAddress()
+
+       if self.mask.intrep == None:
+           raise EmptyNetmask()
+
+       self._precompute()
+
+    def _precompute(self):
+       self.__lower_str = None
+       self.__upper_str = None
+
+       self.network_intrep = self.ip.intrep & self.mask.intrep
+       self.broadcast_intrep = (self.network_intrep |
+                               (pow(2L, 32)-1-self.mask.intrep))
+       self.host_part_intrep = self.ip.intrep - self.network_intrep
+
+       self.__network_str = None
+       self.__broadcast_str = None
+       self.__host_part_str = None
+
+       demand = self.mask.demand
+
+       if demand == DEMAND_NONE:
+           pass
+       elif demand == DEMAND_FILTER or demand == DEMAND_NETWORK:
+           if self.host_part_intrep != 0L:
+               raise BrokenNetworkAddress(self.ip_str(), self.network_str())
+       elif demand == DEMAND_INTERFACE:
+           if self.host_part_intrep == 0L:
+               raise NetworkAddressClash()
+           elif self.broadcast_intrep == self.ip.intrep:
+               raise BroadcastAddressClash()
+       else:
+           raise BadType('Bad value for the demand parameter', demand)
+
+    def network_str(self):
+       if self.__network_str == None:
+           self.__network_str = intrep_to_dotted_decimal(self.network_intrep)
+       return self.__network_str
+
+    def broadcast_str(self):
+       if self.__broadcast_str == None:
+           self.__broadcast_str = intrep_to_dotted_decimal(
+               self.broadcast_intrep)
+       return self.__broadcast_str
+
+    def host_part_str(self):
+       if self.__host_part_str == None:
+           self.__host_part_str = intrep_to_dotted_decimal(
+               self.host_part_intrep)
+       return self.__host_part_str
+
+    def overlaps(self, other):
+       """Returns true if the network overlaps with OTHER.
+
+       OTHER must be a network object or an ipaddr object.  If it
+       is empty this method will always return false.
+
+       """
+
+       if self.network_intrep == None:
+           return 0
+
+       if isinstance(other, ipaddr):
+
+           if other.intrep == None:
+               return 0
+
+           return (self.mask.intrep & other.intrep) == self.network_intrep
+       else:
+           if other.network_intrep == None:
+               return 0
+
+           mask = self.mask.intrep & other.mask.intrep
+           return (mask & self.ip.intrep) == (mask & other.ip.intrep)
+
+    def intersection(self, other):
+       """Return the intersection of the network and OTHER.
+
+       The return value is a network object with DEMAND_FILTER.  If
+       the intersection is empty this method will return None.
+
+       OTHER must be a network object or an ipaddr object.  The
+       intersection will be empty if it is empty.
+       """
+
+       if self.network_intrep == None:
+           return None
+
+       if isinstance(other, ipaddr):
+
+           if other.intrep == None:
+               return None
+
+           prefix_mask = self.mask.intrep
+           short_net = self.network_intrep
+           long_ip = other.intrep
+           result = network(other.intrep, 32, DEMAND_FILTER)
+       else:
+           if other.network_intrep == None:
+               return None
+           
+           if self.mask.netmask_bits < other.mask.netmask_bits:
+               prefix_mask = self.mask.intrep
+               short_net = self.network_intrep
+               long_ip = other.network_intrep
+               result = network(other.network_intrep, other.mask.netmask_bits,
+                                DEMAND_FILTER)
+           else:
+               prefix_mask = other.mask.intrep
+               short_net = other.network_intrep
+               long_ip = self.network_intrep
+               result = network(self.network_intrep, self.mask.netmask_bits,
+                                DEMAND_FILTER)
+
+       if (long_ip & prefix_mask) != (short_net & prefix_mask):
+           return None
+
+       return result
+
+    def is_subset(self, nwip):
+       """Return true if NWIP is a subset of this network.
+
+       NWIP must be a network object or an ipaddr object.
+       """
+
+       if not self.overlaps(nwip):
+           return 0
+
+       if isinstance(nwip, ipaddr):
+           return 1
+
+       return nwip.mask.netmask_bits <= self.mask.netmask_bits
+
+    def is_same_set(self, nwip):
+       """Return true if NWIP contains the same set as this network.
+
+       NWIP must be a network object or an ipaddr object.
+       """
+
+       if isinstance(nwip, ipaddr):
+           return (self.mask.netmask_bits == 32
+                   and self.ip.intrep == nwip.intrep)
+       else:
+           return (self.mask.netmask_bits == nwip.mask.netmask_bits
+                   and self.network_intrep == nwip.network_intrep)
+
+    def subtract(self, nwip):
+       """Create a list of new network objects by subtracting NWIP from self.
+
+       The result consists of networks that together span all
+       IP addresses that are present in self, except those that are
+       present in NWIP.  (The result may be empty or contain several
+       disjoint network objects.)
+
+       Don't use this!  This method is slow.  The ip_set class can do
+       this kind of things in a more efficient way.
+       """
+
+       if not self.overlaps(nwip):
+           # No overlap at all, so NWIP cannot affect the result.
+           return [self]
+
+       if isinstance(nwip, ipaddr):
+           bits = 32
+           intrep = nwip.intrep
+       else:
+           assert isinstance(nwip, network)
+           bits = nwip.mask.netmask_bits
+           intrep = nwip.ip.intrep
+       nets = []
+       while bits > self.mask.netmask_bits:
+           nets.append(network(compute_neighbor(intrep, bits),
+                               bits, DEMAND_FILTER))
+           bits = bits - 1
+       return nets
+
+    def subtract_nwips(self, nwips):
+       """Create a list of new network objects by subtracting NWIPS.
+
+       The result consists of networks that together span all
+       IP addresses that are present in self, except those that are
+       present in NWIPS.  (The result may be empty or contain
+       several disjoint network objects.)  NWIPS should be a list
+       of network or ipaddr objects.
+
+       Don't use this!  This method is slow.  The ip_set class can do
+       this kind of things in a more efficient way.
+       """
+
+       subtracted = [self]
+       for s in nwips:
+           # precondition<A>: SUBTRACTED is a list of networks
+           tmp = []
+           for nw in subtracted:
+               tmp = tmp + nw.subtract(s)
+           subtracted = tmp
+           # postcondition: SUBTRACTED is a list of networks that
+           # spans all IP addresses that were present in
+           # precondition<A>, except those that are present in S.
+
+       return subtracted
+
+    def __compute_lower_upper(self):
+       if self.__lower_str != None:
+           return
+       assert self.network_intrep != None and self.broadcast_intrep != None
+
+       self.__lower_str = intrep_to_dotted_decimal(self.network_intrep + 1)
+       self.__upper_str = intrep_to_dotted_decimal(self.broadcast_intrep - 1)
+
+    def lower_host(self):
+       self.__compute_lower_upper()
+       return self.__lower_str
+
+    def upper_host(self):
+       self.__compute_lower_upper()
+       return self.__upper_str
+
+    def __repr__(self):
+       return _("{network %s/%d}") % (self.ip_str(), self.mask.netmask_bits)
+
+    def ip_str(self):
+       return self.ip.ip_str()
+
+
+class ip_set:
+    def __init__(self, nwip=None):
+       """Create an ip_set.
+
+       If the optional argument NWIP is supplied, the set is
+       initialized to it, otherwise the created set will be empty.
+       NWIP must be a network or ipaddr object.
+       """
+
+       # [[0L, 3L], [5L, 7L]] means 0.0.0.0/29 \ 0.0.0.4/32
+       self.__set = []
+
+       if nwip != None:
+           self.append(nwip)
+
+    def subtract_set(self, other):
+       """Remove all IP-numbers in OTHER from this.
+
+       OTHER should be an ip_set object.
+       """
+
+       self.subtract_list(other.__set)
+
+    def subtract_ips(self, ips):
+       """Remove all IP-numbers in IPS from this.
+
+       IPS should be a list of ipaddr objects.
+       """
+
+       for ip in ips:
+           self.subtract_list([[ip.intrep, ip.intrep]])
+
+    def subtract_list(self, other):
+       # Don't use this method directly, unless you are the test suite.
+       ix = 0
+       iy = 0
+       while ix < len(self.__set) and iy < len(other):
+           if self.__set[ix][1] < other[iy][0]:
+               # The entire range survived.
+               ix = ix + 1
+           elif self.__set[ix][0] > other[iy][1]:
+               # The entire other range is unused, so discard it.
+               iy = iy + 1
+           elif self.__set[ix][0] >= other[iy][0]:
+               if self.__set[ix][1] <= other[iy][1]:
+                   # The entire range is subtracted.
+                   del self.__set[ix]
+               else:
+                   # The start of the range is subtracted, but
+                   # the rest of the range may survive.  (As a matter
+                   # of fact, at least one number *will* survive,
+                   # since there should be a gap between other[iy][1]
+                   # and other[iy+1][0], but we don't use that fact.)
+                   self.__set[ix][0] = other[iy][1] + 1
+                   iy = iy + 1
+           else:
+               # The first part of the range survives.
+               end = self.__set[ix][1]
+               assert self.__set[ix][1] >= other[iy][0]
+               self.__set[ix][1] = other[iy][0] - 1
+               ix = ix + 1
+               if end > other[iy][1]:
+                   # The part that extends past the subtractor may survive.
+                   self.__set[ix:ix] = [[other[iy][1] + 1, end]]
+               # Retain the subtractor -- it may still kill some
+               # other range.
+
+    def add_set(self, other):
+       """Add all IP-numbers in OTHER to this.
+
+       OTHER should be an ip_set object.
+       """
+
+       self.add_list(other.__set)
+
+    def add_list(self, other):
+       # Don't use this method directly, unless you are the test suite.
+       ix = 0
+       iy = 0
+       res = []
+       while ix < len(self.__set) or iy < len(other):
+           # Remove the first range
+           if ix < len(self.__set):
+               if iy < len(other):
+                   if self.__set[ix][0] < other[iy][0]:
+                       rng = self.__set[ix]
+                       ix = ix + 1
+                   else:
+                       rng = other[iy]
+                       iy = iy + 1
+               else:
+                   rng = self.__set[ix]
+                   ix = ix + 1
+           else:
+               rng = other[iy]
+               iy = iy + 1
+
+           # Join this range to the list we already have collected.
+           if len(res) == 0:
+               # This is the first element.
+               res.append(rng)
+           elif rng[0] <= res[-1][1] + 1:
+               # This extends (or is consumed by) the last range.
+               res[-1][1] = max(res[-1][1], rng[1])
+           else:
+               # There is a gap between the previous range and this one.
+               res.append(rng)
+
+       self.__set = res
+
+    def append(self, nwip):
+       """Add NWIP to this.
+
+       NWIP should be a network object or ipaddr object.
+       """
+
+       if isinstance(nwip, network):
+           self.add_network(nwip)
+       else:
+           self.add_ipaddr(nwip)
+
+    def add_network(self, nw):
+       """Add NW to this.
+
+       NW should be a network object.
+       """
+       self.add_list([[nw.network_intrep, nw.broadcast_intrep]])
+
+    def add_range(self, lo_ip, hi_ip):
+       """Add the range of IP numbers specified by LO_IP and HI_IP to this.
+
+       LO_IP and HI_IP should be ipaddr objects.  They specify a
+       range of IP numbers.  Both LO_IP and HI_IP are included in the
+       range.
+       """
+
+       assert lo_ip.intrep != None
+       assert hi_ip.intrep != None
+       assert lo_ip.intrep <= hi_ip.intrep
+       self.add_list([[lo_ip.intrep, hi_ip.intrep]])
+
+    def add_ipaddr(self, ip):
+       """Add IP to this.
+
+       IP should be an ipaddr object.
+       """
+
+       assert ip.intrep != None
+       self.add_list([[ip.intrep, ip.intrep]])
+
+    def complement(self):
+       """Return everything not contained in this ip_set.
+
+       The return value is a new ip_set.  This is not modified.
+       """
+
+       pre = -1L
+       lst = []
+       for [lo, hi] in self.__set:
+           if lo != 0:
+               lst.append([pre+1, lo-1])
+           pre = hi
+       if pre < pow(2L, 32) - 1:
+           lst.append([pre+1, pow(2L, 32) - 1])
+       res = ip_set()
+       res.add_list(lst)
+       return res
+
+    def intersection(self, other):
+       """Return the intersection of this and OTHER.
+
+       The return value is a new ip_set.  This is not modified.
+       OTHER should be an ip_set, network or ipaddr object.
+       """
+
+       res = []
+       ix = 0
+       iy = 0
+       x = copy.deepcopy(self.__set)
+
+       if isinstance(other, ip_set):
+           y = copy.deepcopy(other.__set)
+       elif isinstance(other, network):
+           y = [[other.network_intrep, other.broadcast_intrep]]
+       elif isinstance(other, ipaddr):
+           y = [[other.intrep, other.intrep]]
+       else:
+           raise BadType('expected an ip_set, network or ipaddr argument')
+
+       while ix < len(x) and iy < len(y):
+           if x[ix][1] < y[iy][0]:
+               # The first entry on x doesn't overlap with anything on y.
+               ix = ix + 1
+           elif x[ix][0] > y[iy][1]:
+               # The first entry on y doesn't overlap with anything on x.
+               iy = iy + 1
+           else:
+               # Some overlap exists.
+
+               # Trim away any leading edges.
+               if x[ix][0] < y[iy][0]:
+                   # x starts before y
+                   x[ix][0] = y[iy][0]
+               elif x[ix][0] > y[iy][0]:
+                   # y starts before x
+                   y[iy][0] = x[ix][0]
+
+               # The ranges start at the same point (at least after
+               # the trimming).
+               if x[ix][1] == y[iy][1]:
+                   # The ranges are equal.
+                   res.append(x[ix])
+                   ix = ix + 1
+                   iy = iy + 1
+               elif x[ix][1] < y[iy][1]:
+                   # x is the smaller range
+                   res.append(x[ix])
+                   ix = ix + 1
+               else:
+                   # y is the smaller range
+                   res.append(y[iy])
+                   iy = iy + 1
+
+       result = ip_set()
+       result.add_list(res)
+       return result
+
+
+    def as_list_of_networks(self):
+       """Return this set as a list of networks.
+
+       The returned value is a list of network objects, that are
+       created with DEMAND_FILTER.  This method may be expensive, so
+       it should only be used when necessary.
+       """
+
+       bm = []
+       for [a, b] in self.__set:
+
+           lomask = 1L
+           lobit = 1L
+           himask = pow(2L, 32)-2
+           bits = 32
+           while a <= b:
+               if a & lomask != 0L:
+                   bm.append((bits, a))
+                   a = a + lobit
+               elif b & lomask != lomask:
+                   bm.append((bits, b & himask))
+                   b = b - lobit
+               else:
+                   lomask = (lomask << 1) | 1
+                   lobit = lobit << 1
+                   himask = himask ^ lobit
+                   bits = bits - 1
+                   assert(bits >= 0)
+       bm.sort()
+       res = []
+       for (mask, ip) in bm:
+           res.append(network(ip, mask, DEMAND_FILTER))
+       return res
+
+    def as_list_of_ranges(self):
+       """Return the set of IP addresses as a list of ranges.
+
+       Each range is a list of two long numbers.  Sample return
+       value: [[1L, 3L], [0x7f000001L, 0x7f000001L]], meaning
+       the set 0.0.0.1, 0.0.0.2, 0.0.0.3, 127.0.0.1.
+       """
+
+       # This method is currently very cheap, since this is the
+       # current internal representation.
+
+       return self.__set
+
+    def as_str_range(self):
+       """Return the set as a string, such as "1.2.3.4-1.2.3.8".
+
+       The returned value always has the form a.b.c.d-e.f.g.h.
+       Raises SetNotRepresentable if the set cannot be represented as a
+       single interval, or if it is the empty set.
+       """
+       if len(self.__set) != 1:
+           raise SetNotRepresentable()
+       return "%s-%s" % (intrep_to_dotted_decimal(self.__set[0][0]),
+                         intrep_to_dotted_decimal(self.__set[0][1]))
+
+    def contains(self, ip):
+       """Return true if IP is contained in the set.
+
+       IP should be an ipaddr object.  The empty ipaddr is never contained.
+       """
+
+       if ip.intrep == None:
+           return 0
+
+       for [lo, hi] in self.__set:
+           if lo <= ip.intrep <= hi:
+               return 1
+       return 0
+
+    def overlaps(self, nwip):
+       """Return true if NWIP overlaps the set of IP addresses.
+
+       NWIP may be an ipaddr, network or ip_set object.
+       """
+
+       if isinstance(nwip, ipaddr):
+           return self.contains(nwip)
+       elif isinstance(nwip, ip_set):
+           # This could be optimized -- we don't really need
+           # to compute the intersection.
+           return not self.intersection(nwip).is_empty()
+       elif isinstance(nwip, network):
+           wanted_low = nwip.network_intrep
+           wanted_high = nwip.broadcast_intrep
+           if wanted_low == None or wanted_high == None:
+               return 0
+           for [lo, hi] in self.__set:
+               if lo > wanted_high:
+                   # We are past the interresting interval.
+                   return 0
+               if lo >= wanted_low or hi >= wanted_low:
+                   return 1
+           return 0
+       else:
+           raise BadType('Expected an ipaddr, ip_set or network instance')
+
+    def is_empty(self):
+       """Return true if this ip_set is empty.
+       """
+
+       return len(self.__set) == 0
+
+    def any_ip(self):
+       """Return one of the IP addresses contained in ip_set.
+
+       This method may only be called if the set is non-empty.  You
+       can use the is_empty method to test for emptiness.
+
+       This picks an IP address from the set and returns it as an
+       ipaddr object.  Given the same set of IP addresses, this
+       method will always return the same IP address, but which IP
+       address it chooses is explicitly undocumented and may change
+       if the underlying implementation of ip_set ever changes.
+       """
+
+       assert not self.is_empty()
+       return ipaddr(self.__set[0][0])
+
+    def __str__(self):
+       res = []
+       for rng in self.__set:
+           if rng[0] == rng[1]:
+               res.append(intrep_to_dotted_decimal(rng[0]))
+           else:
+               res.append('%s-%s' % (intrep_to_dotted_decimal(rng[0]),
+                                     intrep_to_dotted_decimal(rng[1])))
+       return '<ipaddr.ip_set(%s)>' % string.join(res, ', ')
+
+complete_network = network(0L, 0, DEMAND_FILTER)
+complete_set = ip_set(complete_network)
+broadcast_network = network('255.255.255.255', 32, DEMAND_FILTER)
+broadcast_set = ip_set(broadcast_network)
+
+def compute_neighbor(intrep, bits):
+    xor_mask = intrep_with_bit_set(bits)
+    and_mask = bits_to_intrep(bits)
+    return (intrep ^ xor_mask) & and_mask
+
+
+if __name__ == '__main__':
+    # Test/demo code.  With no arguments, this will print a page
+    # of data that can be useful when trying to interpret an
+    # ipnumber/netmask pair.  With two arguments, it will print some
+    # information about the IP number and netmask that was entered.
+
+    import sys
+    if len(sys.argv) == 1:
+       print "Netmasks\n========"
+       for i in range(0, 17):
+           if i != 16:
+               print '%2d' % i,
+               print '%-13s' % netmask(i, DEMAND_NONE).netmask_str(),
+           else:
+               print ' ' * 16,
+           print i + 16, '%-16s' % netmask(i + 16, DEMAND_NONE).netmask_str()
+       print _("\n\nIP intervals\n============")
+       for i in range(9):
+           for j in range(0, 4):
+               print '%2d' % (8*j + i),
+           print '%3d' % (netmask(i, DEMAND_NONE).intrep >> 24),
+           x = 0
+           need_break = 0
+           if i < 8:
+               for j in range(0, 256, pow(2, 8-i)):
+                   if need_break:
+                       print
+                       print ' ' * 15,
+                       need_break = 0
+                   print '%3d-%-3d' % (j, j + pow(2, 8-i)-1),
+                   x = x + 1
+                   if x % 8 == 0:
+                       need_break = 1
+           else:
+               print '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13...',
+           print
+       sys.exit(0)
+
+    if len(sys.argv) != 3:
+       sys.stderr.write(_("Usage: python ipaddr.py IP_ADDRESS NETMASK\n"))
+       sys.exit(1)
+    nw = network(sys.argv[1], sys.argv[2], DEMAND_NONE)
+    print nw
+    print "IP address:       ", nw.ip.ip_str()
+    print "Netmask:          ", nw.mask.netmask_str(),
+    print " (/" + nw.mask.netmask_bits_str + ")"
+    print "Network address:  ", nw.network_str()
+    print "Broadcast address:", nw.broadcast_str()
diff --git a/make-secnet-sites.py b/make-secnet-sites.py
new file mode 100755 (executable)
index 0000000..8a64338
--- /dev/null
@@ -0,0 +1,469 @@
+#! /usr/bin/env python
+# Copyright (C) 2001 Stephen Early <steve@greenend.org.uk>
+#
+# 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
+
+"""VPN sites file manipulation.
+
+This program enables VPN site descriptions to be submitted for
+inclusion in a central database, and allows the resulting database to
+be turned into a secnet configuration file.
+
+A database file can be turned into a secnet configuration file simply:
+make-secnet-sites.py [infile [outfile]]
+
+It would be wise to run secnet with the "--just-check-config" option
+before installing the output on a live system.
+
+The program expects to be invoked via userv to manage the database; it
+relies on the USERV_USER and USERV_GROUP environment variables. The
+command line arguments for this invocation are:
+
+make-secnet-sites.py -u header-filename groupfiles-directory output-file \
+  group
+
+All but the last argument are expected to be set by userv; the 'group'
+argument is provided by the user. A suitable userv configuration file
+fragment is:
+
+reset
+no-disconnect-hup
+no-suppress-args
+cd ~/secnet/sites-test/
+execute ~/secnet/secnet/make-secnet-sites.py -u vpnheader groupfiles sites
+
+This program is part of secnet. It relies on the "ipaddr" library from
+Cendio Systems AB.
+
+"""
+
+import string
+import time
+import sys
+import os
+import ipaddr
+
+VERSION="0.1.3"
+
+class vpn:
+       def __init__(self,name):
+               self.name=name
+               self.allow_defs=0
+               self.locations={}
+               self.defs={}
+
+class location:
+       def __init__(self,name,vpn):
+               self.group=None
+               self.name=name
+               self.allow_defs=1
+               self.vpn=vpn
+               self.sites={}
+               self.defs={}
+
+class site:
+       def __init__(self,name,location):
+               self.name=name
+               self.allow_defs=1
+               self.location=location
+               self.defs={}
+
+class nets:
+       def __init__(self,w):
+               self.w=w
+               self.set=ipaddr.ip_set()
+               for i in w[1:]:
+                       x=string.split(i,"/")
+                       self.set.append(ipaddr.network(x[0],x[1],
+                               ipaddr.DEMAND_NETWORK))
+       def subsetof(self,s):
+               # I'd like to do this:
+               # return self.set.is_subset(s)
+               # but there isn't an is_subset() method
+               # Instead we see if we intersect with the complement of s
+               sc=s.set.complement()
+               i=sc.intersection(self.set)
+               return i.is_empty()
+       def out(self):
+               rn=''
+               if (self.w[0]=='restrict-nets'): rn='# '
+               return '%s%s %s;'%(rn,self.w[0],
+                       string.join(map(lambda x:'"%s/%s"'%(x.ip_str(),
+                               x.mask.netmask_bits_str),
+                               self.set.as_list_of_networks()),","))
+
+class dhgroup:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return 'dh diffie-hellman("%s","%s");'%(self.w[1],self.w[2])
+
+class hash:
+       def __init__(self,w):
+               self.w=w
+               if (w[1]!='md5' and w[1]!='sha1'):
+                       complain("unknown hash type %s"%(w[1]))
+       def out(self):
+               return 'hash %s;'%(self.w[1])
+
+class email:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return '# Contact email address: <%s>'%(self.w[1])
+
+class num:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return '%s %s;'%(self.w[0],self.w[1])
+
+class address:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return 'address "%s"; port %s;'%(self.w[1],self.w[2])
+
+class rsakey:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return 'key rsa-public("%s","%s");'%(self.w[2],self.w[3])
+
+class mobileoption:
+       def __init__(self,w):
+               self.w=w
+       def out(self):
+               return 'netlink-options "soft";'
+
+def complain(msg):
+       global complaints
+       print ("%s line %d: "%(file,line))+msg
+       complaints=complaints+1
+def moan(msg):
+       global complaints
+       print msg;
+       complaints=complaints+1
+
+# We don't allow redefinition of properties (because that would allow things
+# like restrict-nets to be redefined, which would be bad)
+def set(obj,defs,w):
+       if (obj.allow_defs | allow_defs):
+               if (obj.defs.has_key(w[0])):
+                       complain("%s is already defined"%(w[0]))
+               else:
+                       t=defs[w[0]]
+                       obj.defs[w[0]]=t(w)
+
+# Process a line of configuration file
+def pline(i):
+       global allow_defs, group, current_vpn, current_location, current_object
+       w=string.split(i)
+       if len(w)==0: return
+       keyword=w[0]
+       if keyword=='end-definitions':
+               allow_defs=0
+               current_vpn=None
+               current_location=None
+               current_object=None
+               return
+       if keyword=='vpn':
+               if vpns.has_key(w[1]):
+                       current_vpn=vpns[w[1]]
+                       current_object=current_vpn
+               else:
+                       if allow_defs:
+                               current_vpn=vpn(w[1])
+                               vpns[w[1]]=current_vpn
+                               current_object=current_vpn
+                       else:
+                               complain("no new VPN definitions allowed")
+               return
+       if (current_vpn==None):
+               complain("no VPN defined yet")
+               return
+       # Keywords that can apply at all levels
+       if mldefs.has_key(w[0]):
+               set(current_object,mldefs,w)
+               return
+       if keyword=='location':
+               if (current_vpn.locations.has_key(w[1])):
+                       current_location=current_vpn.locations[w[1]]
+                       current_object=current_location
+                       if (group and not allow_defs and 
+                               current_location.group!=group):
+                               complain(("must be group %s to access "+
+                                       "location %s")%(current_location.group,
+                                       w[1]))
+               else:
+                       if allow_defs:
+                               if reserved.has_key(w[1]):
+                                       complain("reserved location name")
+                                       return
+                               current_location=location(w[1],current_vpn)
+                               current_vpn.locations[w[1]]=current_location
+                               current_object=current_location
+                       else:
+                               complain("no new location definitions allowed")
+               return
+       if (current_location==None):
+               complain("no locations defined yet")
+               return
+       if keyword=='group':
+               current_location.group=w[1]
+               return
+       if keyword=='site':
+               if (current_location.sites.has_key(w[1])):
+                       current_object=current_location.sites[w[1]]
+               else:
+                       if reserved.has_key(w[1]):
+                               complain("reserved site name")
+                               return
+                       current_object=site(w[1],current_location)
+                       current_location.sites[w[1]]=current_object
+               return
+       if keyword=='endsite':
+               if isinstance(current_object,site):
+                       current_object=current_object.location
+               else:
+                       complain("not currently defining a site")
+               return
+       # Keywords that can only apply to sites
+       if isinstance(current_object,site):
+               if sitedefs.has_key(w[0]):
+                       set(current_object,sitedefs,w)
+                       return
+       else:
+               if sitedefs.has_key(w[0]):
+                       complain("keyword '%s' can only be used in the "
+                               "context of a site definition"%(w[0]))
+                       return
+       complain("unknown keyword '%s'"%(w[0]))
+
+def pfile(name,lines):
+       global file,line
+       file=name
+       line=0
+       for i in lines:
+               line=line+1
+               if (i[0]=='#'): continue
+               if (i[len(i)-1]=='\n'): i=i[:len(i)-1] # strip trailing LF
+               pline(i)
+
+def outputsites(w):
+       w.write("# secnet sites file autogenerated by make-secnet-sites.py "
+               +"version %s\n"%VERSION)
+       w.write("# %s\n\n"%time.asctime(time.localtime(time.time())))
+
+       # Raw VPN data section of file
+       w.write("vpn-data {\n")
+       for i in vpns.values():
+               w.write("  %s {\n"%i.name)
+               for d in i.defs.values():
+                       w.write("    %s\n"%d.out())
+               w.write("\n")
+               for l in i.locations.values():
+                       w.write("    %s {\n"%l.name)
+                       for d in l.defs.values():
+                               w.write("      %s\n"%d.out())
+                       for s in l.sites.values():
+                               w.write("      %s {\n"%s.name)
+                               w.write('        name "%s/%s/%s";\n'%
+                                       (i.name,l.name,s.name))
+                               for d in s.defs.values():
+                                       w.write("        %s\n"%d.out())
+                               w.write("      };\n")
+                       w.write("    };\n")
+               w.write("  };\n")
+       w.write("};\n")
+
+       # Per-VPN flattened lists
+       w.write("vpn {\n")
+       for i in vpns.values():
+               w.write("  %s {\n"%(i.name))
+               for l in i.locations.values():
+                       slist=map(lambda x:"vpn-data/%s/%s/%s"%
+                               (i.name,l.name,x.name),
+                               l.sites.values())
+                       w.write("    %s %s;\n"%(l.name,string.join(slist,",")))
+               w.write("\n    all-sites %s;\n"%
+                       string.join(i.locations.keys(),","))
+               w.write("  };\n")
+       w.write("};\n")
+
+       # Flattened list of sites
+       w.write("all-sites %s;\n"%string.join(map(lambda x:"vpn/%s/all-sites"%
+               x,vpns.keys()),","))
+
+# Are we being invoked from userv?
+service=0
+# If we are, which group does the caller want to modify?
+group=None
+
+vpns={}
+allow_defs=1
+current_vpn=None
+current_location=None
+current_object=None
+
+line=0
+file=None
+complaints=0
+
+# Things that can be defined at any level
+mldefs={
+       'dh':dhgroup,
+       'hash':hash,
+       'contact':email,
+       'key-lifetime':num,
+       'setup-retries':num,
+       'setup-timeout':num,
+       'wait-time':num,
+       'renegotiate-time':num,
+       'restrict-nets':nets
+       }
+
+# Things that can only be defined for sites
+sitedefs={
+       'address':address,
+       'networks':nets,
+       'pubkey':rsakey,
+       'mobile':mobileoption
+       }
+
+# Reserved vpn/location/site names
+reserved={'all-sites':None}
+reserved.update(mldefs)
+reserved.update(sitedefs)
+
+# Each site must have the following defined at some level:
+required={
+       'dh':"Diffie-Hellman group",
+       'networks':"network list",
+       'pubkey':"public key",
+       'hash':"hash function"
+       }
+
+if len(sys.argv)<2:
+       pfile("stdin",sys.stdin.readlines())
+       of=sys.stdout
+else:
+       if sys.argv[1]=='-u':
+               if len(sys.argv)!=6:
+                       print "Wrong number of arguments"
+                       sys.exit(1)
+               service=1
+               header=sys.argv[2]
+               groupfiledir=sys.argv[3]
+               sitesfile=sys.argv[4]
+               group=sys.argv[5]
+               if not os.environ.has_key("USERV_USER"):
+                       print "Environment variable USERV_USER not found"
+                       sys.exit(1)
+               user=os.environ["USERV_USER"]
+               # Check that group is in USERV_GROUP
+               if not os.environ.has_key("USERV_GROUP"):
+                       print "Environment variable USERV_GROUP not found"
+                       sys.exit(1)
+               ugs=os.environ["USERV_GROUP"]
+               ok=0
+               for i in string.split(ugs):
+                       if group==i: ok=1
+               if not ok:
+                       print "caller not in group %s"%group
+                       sys.exit(1)
+               f=open(header)
+               pfile(header,f.readlines())
+               f.close()
+               userinput=sys.stdin.readlines()
+               pfile("user input",userinput)
+       else:
+               if len(sys.argv)>3:
+                       print "Too many arguments"
+                       sys.exit(1)
+               f=open(sys.argv[1])
+               pfile(sys.argv[1],f.readlines())
+               f.close()
+               of=sys.stdout
+               if len(sys.argv)>2:
+                       of=open(sys.argv[2],'w')
+
+# Sanity check section
+
+# Delete locations that have no sites defined
+for i in vpns.values():
+       for l in i.locations.keys():
+               if (len(i.locations[l].sites.values())==0):
+                       del i.locations[l]
+
+# Delete VPNs that have no locations with sites defined
+for i in vpns.keys():
+       if (len(vpns[i].locations.values())==0):
+               del vpns[i]
+
+# Check all sites
+for i in vpns.values():
+       if i.defs.has_key('restrict-nets'):
+               vr=i.defs['restrict-nets']
+       else:
+               vr=None
+       for l in i.locations.values():
+               if l.defs.has_key('restrict-nets'):
+                       lr=l.defs['restrict-nets']
+                       if (not lr.subsetof(vr)):
+                               moan("location %s/%s restrict-nets is invalid"%
+                                       (i.name,l.name))
+               else:
+                       lr=vr
+               for s in l.sites.values():
+                       sn="%s/%s/%s"%(i.name,l.name,s.name)
+                       for r in required.keys():
+                               if (not (s.defs.has_key(r) or
+                                       l.defs.has_key(r) or
+                                       i.defs.has_key(r))):
+                                       moan("site %s missing parameter %s"%
+                                               (sn,r))
+                       if s.defs.has_key('restrict-nets'):
+                               sr=s.defs['restrict-nets']
+                               if (not sr.subsetof(lr)):
+                                       moan("site %s restrict-nets not valid"%
+                                               sn)
+                       else:
+                               sr=lr
+                       if not s.defs.has_key('networks'): continue
+                       nets=s.defs['networks']
+                       if (not nets.subsetof(sr)):
+                               moan("site %s networks exceed restriction"%sn)
+
+
+if complaints>0:
+       if complaints==1: print "There was 1 problem."
+       else: print "There were %d problems."%(complaints)
+       sys.exit(1)
+
+if service:
+       # Put the user's input into their group file, and rebuild the main
+       # sites file
+       f=open(groupfiledir+"-tmp/"+group,'w')
+       f.write("# Section submitted by user %s, %s\n"%
+               (user,time.asctime(time.localtime(time.time()))))
+       f.write("# Checked by make-secnet-sites.py version %s\n\n"%VERSION)
+       for i in userinput: f.write(i)
+       f.write("\n")
+       f.close()
+       os.rename(groupfiledir+"-tmp/"+group,groupfiledir+"/"+group)
+       # XXX rebuild main sites file!
+else:
+       outputsites(of)
diff --git a/md5.c b/md5.c
index 12217d7aa44c0bdde9dcc069c5054eef4dbb861e..8c962d9a4bac76ee2bf09f9230964c5c14b6f8f0 100644 (file)
--- a/md5.c
+++ b/md5.c
@@ -279,7 +279,7 @@ void md5_module(dict_t *dict)
     uint8_t digest[16];
     int i;
 
-    st=safe_malloc(sizeof(*st),"netlink_module");
+    st=safe_malloc(sizeof(*st),"md5_module");
     st->cl.description="md5";
     st->cl.type=CL_HASH;
     st->cl.apply=NULL;
index 0d16c2f89893a08aeeb1ee0cefe1fd65f86686ce..79ba9a39108cbf1b01385ab118991b9aac140957 100644 (file)
--- a/modules.c
+++ b/modules.c
@@ -12,6 +12,7 @@ extern init_module dh_module;
 extern init_module md5_module;
 extern init_module slip_module;
 extern init_module tun_module;
+extern init_module sha1_module;
 
 void init_builtin_modules(dict_t *dict)
 {
@@ -27,4 +28,5 @@ void init_builtin_modules(dict_t *dict)
     md5_module(dict);
     slip_module(dict);
     tun_module(dict);
+    sha1_module(dict);
 }
index 886ce058cd6099c4a9d3a2bc7af16f6b9a5bde9e..708205b9eb6d0743d30188c0a8c4f5651197d647 100644 (file)
--- a/netlink.c
+++ b/netlink.c
@@ -274,6 +274,12 @@ static void netlink_packet_deliver(struct netlink *st,
        return;
     }
     
+    /* XXX we're going to need an extra value 'allow_route' for the
+       source of the packet. It's always True for packets from the
+       host. For packets from tunnels, we consult the client
+       options. If !allow_route and the destination is a tunnel that
+       also doesn't allow routing, we must reject the packet with an
+       'administratively prohibited' or something similar ICMP. */
     if (!client) {
        /* Origin of packet is host or secnet. Might be for a tunnel. */
        best_quality=0;
@@ -323,6 +329,9 @@ static void netlink_packet_deliver(struct netlink *st,
        }
     } else { /* client is set */
        /* We know the origin is a tunnel - packet must be for the host */
+       /* XXX THIS IS NOT NECESSARILY TRUE, AND NEEDS FIXING */
+       /* THIS FUNCTION MUST JUST DELIVER THE PACKET: IT MUST ASSUME
+          THE PACKET HAS ALREADY BEEN CHECKED */
        if (subnet_matches_list(&st->networks,dest)) {
            st->deliver_to_host(st->dst,NULL,buf);
            BUF_ASSERT_FREE(buf);
@@ -456,6 +465,7 @@ static void netlink_incoming(void *sst, void *cid, struct buffer_if *buf)
        return;
     }
     if (client) {
+       /* Check for free routing */
        if (!subnet_matches_list(&st->networks,dest)) {
            string_t s,d;
            s=ipaddr_to_string(source);
@@ -502,7 +512,7 @@ static void netlink_set_quality(void *sst, void *cid, uint32_t quality)
 static void *netlink_regnets(void *sst, struct subnet_list *nets,
                             netlink_deliver_fn *deliver, void *dst,
                             uint32_t max_start_pad, uint32_t max_end_pad,
-                            bool_t hard_routes, string_t client_name)
+                            uint32_t options, string_t client_name)
 {
     struct netlink *st=sst;
     struct netlink_client *c;
@@ -511,31 +521,19 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
            "max_start_pad=%d, max_end_pad=%d\n",
            nets->entries,max_start_pad,max_end_pad);
 
-    if (!hard_routes && !st->set_route) {
+    if ((options&NETLINK_OPTION_SOFTROUTE) && !st->set_route) {
        Message(M_ERROR,"%s: this netlink device does not support "
                "soft routes.\n");
        return NULL;
     }
 
-    if (!hard_routes) {
+    if (options&NETLINK_OPTION_SOFTROUTE) {
        /* XXX for now we assume that soft routes require root privilege;
-          this may not always be true. */
+          this may not always be true. The device driver can tell us. */
        require_root_privileges=True;
        require_root_privileges_explanation="netlink: soft routes";
     }
 
-#if 0
-    /* XXX POLICY: do we check nets against local networks?  If we do,
-       that prevents things like laptop tunnels working.  Perhaps we
-       can have a configuration option for this.  Or, if the admin
-       really doesn't want remote sites to be able to claim local
-       addresses, he can list them in exclude-remote-networks. */
-    if (subnet_lists_intersect(&st->networks,nets)) {
-       Message(M_ERROR,"%s: site %s specifies networks that "
-               "intersect with our local networks\n",st->name,client_name);
-       return False;
-    }
-#endif /* 0 */
     /* Check that nets do not intersect st->exclude_remote_networks;
        refuse to register if they do. */
     if (subnet_lists_intersect(&st->exclude_remote_networks,nets)) {
@@ -550,7 +548,7 @@ static void *netlink_regnets(void *sst, struct subnet_list *nets,
     c->deliver=deliver;
     c->dst=dst;
     c->name=client_name; /* XXX copy it? */
-    c->hard_routes=hard_routes;
+    c->options=options;
     c->link_quality=LINK_QUALITY_DOWN;
     c->next=st->clients;
     st->clients=c;
@@ -569,8 +567,10 @@ static void netlink_dump_routes(struct netlink *st)
     Message(M_INFO,"%s: routing table:\n",st->name);
     for (i=0; i<st->n_routes; i++) {
        net=subnet_to_string(&st->routes[i].net);
-       Message(M_INFO,"%s -> tunnel %s (%s,%s)\n",net,st->routes[i].c->name,
+       Message(M_INFO,"%s -> tunnel %s (%s,%s route,%s)\n",net,
+               st->routes[i].c->name,
                st->routes[i].hard?"hard":"soft",
+               st->routes[i].allow_route?"free":"restricted",
                st->routes[i].up?"up":"down");
        free(net);
     }
@@ -610,10 +610,13 @@ static void netlink_phase_hook(void *sst, uint32_t new_phase)
        for (j=0; j<c->networks->entries; j++) {
            st->routes[i].net=c->networks->list[j];
            st->routes[i].c=c;
-           st->routes[i].up=c->hard_routes; /* Hard routes are always up;
-                                               soft routes default to down */
+           /* Hard routes are always up;
+              soft routes default to down */
+           st->routes[i].up=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
            st->routes[i].kup=False;
-           st->routes[i].hard=c->hard_routes;
+           st->routes[i].hard=c->options&NETLINK_OPTION_SOFTROUTE?False:True;
+           st->routes[i].allow_route=c->options&NETLINK_OPTION_ALLOW_ROUTE?
+               True:False;
            i++;
        }
     }
index faf3282ad9fc666ced4bbdd476e803f9bf190e9d..f1144441fb094623781435889d709c5d9f2c5a4d 100644 (file)
--- a/netlink.h
+++ b/netlink.h
@@ -11,13 +11,14 @@ struct netlink_client {
     void *dst;
     string_t name;
     uint32_t link_quality;
-    bool_t hard_routes;
+    uint32_t options;
     struct netlink_client *next;
 };
 
 struct netlink_route {
     struct subnet net;
     bool_t hard;
+    bool_t allow_route;
     bool_t up;
     bool_t kup;
     struct netlink_client *c;
diff --git a/rsa.c b/rsa.c
index 03318ba16ba75849207a3be6311729fdceb56d43..81fb1b5cd7ea5587502043fd938409e652d8d2c3 100644 (file)
--- a/rsa.c
+++ b/rsa.c
@@ -62,6 +62,10 @@ static string_t rsa_sign(void *sst, uint8_t *data, uint32_t datalen)
 
     msize=mpz_sizeinbase(&st->n, 16);
 
+    if (datalen*2+4>=msize) {
+       fatal("rsa_sign: message too big\n");
+    }
+
     strcpy(buff,"0001");
 
     for (i=0; i<datalen; i++) {
@@ -69,7 +73,7 @@ static string_t rsa_sign(void *sst, uint8_t *data, uint32_t datalen)
        buff[5+i*2]=hexchars[data[i]&0xf];
     }
     buff[4+datalen*2]=0;
-
+    
     for (i=datalen*2+4; i<msize; i++)
        buff[i]='f';
 
index 3ac5656b63e79799fc12fe43868c650fd85b1f39..c85f90f3aab21e6166998f752e5bd81d87b13bc9 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -387,10 +387,12 @@ typedef void netlink_link_quality_fn(void *st, void *cid, uint32_t quality);
    client identifier.  'hard_route' indicates whether the routes being
    registered are permanent (hard) or temporary (soft); some types of
    netlink device can only cope with hard routes. */
+#define NETLINK_OPTION_SOFTROUTE    1
+#define NETLINK_OPTION_ALLOW_ROUTE  2
 typedef void *netlink_regnets_fn(void *st, struct subnet_list *networks,
                                 netlink_deliver_fn *deliver, void *dst,
                                 uint32_t max_start_pad, uint32_t max_end_pad,
-                                bool_t hard_routes, string_t client_name);
+                                uint32_t options, string_t client_name);
 
 struct netlink_if {
     void *st;
diff --git a/sha1.c b/sha1.c
new file mode 100644 (file)
index 0000000..c378a9d
--- /dev/null
+++ b/sha1.c
@@ -0,0 +1,353 @@
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+Note: parts of this file have been removed or modified to work in secnet.
+Instead of using this file in new projects, I suggest you use the
+unmodified version. SDE.
+
+-----------------
+Modified 7/98 
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+Corrected a problem which generated improper hash values on 16 bit machines
+Routine SHA1Update changed from
+       void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+len)
+to
+       void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+long len)
+
+The 'len' parameter was declared an int which works fine on 32 bit machines.
+However, on 16 bit machines an int is too small for the shifts being done
+against
+it.  This caused the hash function to generate incorrect values if len was
+greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+
+Since the file IO in main() reads 16K at a time, any file 8K or larger would
+be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+"a"s).
+
+I also changed the declaration of variables i & j in SHA1Update to 
+unsigned long from unsigned int for the same reason.
+
+These changes should make no difference to any 32 bit implementations since
+an
+int and a long are the same size in those environments.
+
+--
+I also corrected a few compiler warnings generated by Borland C.
+1. Added #include <process.h> for exit() prototype
+2. Removed unused variable 'j' in SHA1Final
+3. Changed exit(0) to return(0) at end of main.
+
+ALL changes I made can be located by searching for comments containing 'JHB'
+-----------------
+Modified 8/98
+By Steve Reid <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> and used return() instead of exit()
+2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+
+-----------------
+Modified 4/01
+By Saul Kravitz <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.  
+
+
+*/
+
+/*
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define SHA1HANDSOFF  */
+
+#include "secnet.h"
+#include <stdio.h>
+#include <string.h>
+
+#define SHA1HANDSOFF
+
+#if 0
+#ifndef  i386   /* For ALPHA  (SAK) */
+#define LITTLE_ENDIAN 
+typedef          long int int64;
+typedef unsigned long int uint64;
+typedef          int int32;
+typedef unsigned int uint32;
+#else  /*i386*/
+#define LITTLE_ENDIAN 
+typedef          long long int int64;
+typedef unsigned long long int uint64;
+typedef          long int int32;
+typedef unsigned long int uint32;
+#endif /*i386*/
+#endif /* 0 */
+
+/* Get types and defines from the secnet configuration */
+typedef int64_t int64;
+typedef uint64_t uint64;
+typedef int32_t int32;
+typedef uint32_t uint32;
+
+/* #include <process.h> */     /* prototype for exit() - JHB */
+/* Using return() instead of exit() - SWR */
+
+typedef struct {
+    uint32 state[5];
+    uint32 count[2];
+    unsigned char buffer[64];
+} SHA1_CTX;
+
+void SHA1Transform(uint32 state[5], unsigned char const buffer[64]);
+void SHA1Init(SHA1_CTX* context);
+void SHA1Update(SHA1_CTX* context, unsigned char const * data, uint32 len);
+/* JHB */
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#ifndef WORDS_BIGENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+    |(rol(block->l[i],8)&0x00FF00FF))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+    ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+#ifdef VERBOSE  /* SAK */
+void SHAPrintContext(SHA1_CTX *context, char *msg){
+  printf("%s (%d,%d) %x %x %x %x %x\n",
+        msg,
+        context->count[0], context->count[1], 
+        context->state[0],
+        context->state[1],
+        context->state[2],
+        context->state[3],
+        context->state[4]);
+}
+#endif
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(uint32 state[5], unsigned char const buffer[64])
+{
+uint32 a, b, c, d, e;
+typedef union {
+    unsigned char c[64];
+    uint32 l[16];
+} CHAR64LONG16;
+CHAR64LONG16* block;
+#ifdef SHA1HANDSOFF
+static unsigned char workspace[64];
+    block = (CHAR64LONG16*)workspace;
+    memcpy(block, buffer, 64);
+#else
+    block = (CHAR64LONG16*)buffer;
+#endif
+    /* Copy context->state[] to working vars */
+    a = state[0];
+    b = state[1];
+    c = state[2];
+    d = state[3];
+    e = state[4];
+    /* 4 rounds of 20 operations each. Loop unrolled. */
+    R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+    R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+    R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+    R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+    R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+    R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+    R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+    R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+    R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+    R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+    R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+    R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+    R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+    R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+    R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+    R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+    R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+    R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+    R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+    R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+    /* Add the working vars back into context.state[] */
+    state[0] += a;
+    state[1] += b;
+    state[2] += c;
+    state[3] += d;
+    state[4] += e;
+    /* Wipe variables */
+    a = b = c = d = e = 0;
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(SHA1_CTX* context)
+{
+    /* SHA1 initialization constants */
+    context->state[0] = 0x67452301;
+    context->state[1] = 0xEFCDAB89;
+    context->state[2] = 0x98BADCFE;
+    context->state[3] = 0x10325476;
+    context->state[4] = 0xC3D2E1F0;
+    context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(SHA1_CTX* context, unsigned char const* data, uint32 len)      /*
+JHB */
+{
+uint32 i, j;   /* JHB */
+
+#ifdef VERBOSE
+    SHAPrintContext(context, "before");
+#endif
+    j = (context->count[0] >> 3) & 63;
+    if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;
+    context->count[1] += (len >> 29);
+    if ((j + len) > 63) {
+        memcpy(&context->buffer[j], data, (i = 64-j));
+        SHA1Transform(context->state, context->buffer);
+        for ( ; i + 63 < len; i += 64) {
+            SHA1Transform(context->state, &data[i]);
+        }
+        j = 0;
+    }
+    else i = 0;
+    memcpy(&context->buffer[j], &data[i], len - i);
+#ifdef VERBOSE
+    SHAPrintContext(context, "after ");
+#endif
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
+{
+uint32 i;      /* JHB */
+unsigned char finalcount[8];
+
+    for (i = 0; i < 8; i++) {
+        finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+         >> ((3-(i & 3)) * 8) ) & 255);  /* Endian independent */
+    }
+    SHA1Update(context, (unsigned char *)"\200", 1);
+    while ((context->count[0] & 504) != 448) {
+        SHA1Update(context, (unsigned char *)"\0", 1);
+    }
+    SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform()
+*/
+    for (i = 0; i < 20; i++) {
+        digest[i] = (unsigned char)
+         ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+    }
+    /* Wipe variables */
+    i = 0;     /* JHB */
+    memset(context->buffer, 0, 64);
+    memset(context->state, 0, 20);
+    memset(context->count, 0, 8);
+    memset(finalcount, 0, 8);  /* SWR */
+#ifdef SHA1HANDSOFF  /* make SHA1Transform overwrite it's own static vars */
+    SHA1Transform(context->state, context->buffer);
+#endif
+}
+  
+/*************************************************************/
+
+/* Everything below here is the interface to secnet */
+static void *sha1_init(void)
+{
+    SHA1_CTX *ctx;
+
+    ctx=safe_malloc(sizeof(*ctx),"sha1_init");
+    SHA1Init(ctx);
+
+    return ctx;
+}
+
+static void sha1_update(void *sst, uint8_t const *buf, uint32_t len)
+{
+    SHA1_CTX *ctx=sst;
+
+    SHA1Update(ctx,buf,len);
+}
+
+static void sha1_final(void *sst, uint8_t *digest)
+{
+    SHA1_CTX *ctx=sst;
+
+    SHA1Final(digest,ctx);
+    free(ctx);
+}
+
+struct sha1 {
+    closure_t cl;
+    struct hash_if ops;
+};
+
+init_module sha1_module;
+void sha1_module(dict_t *dict)
+{
+    struct sha1 *st;
+    void *ctx;
+    string_t testinput="abcdbcdecdefdefgefghfghigh"
+       "ijhijkijkljklmklmnlmnomnopnopq";
+    uint8_t expected[20]=
+    { 0x84,0x98,0x3e,0x44,
+      0x1c,0x3b,0xd2,0x6e,
+      0xba,0xae,0x4a,0xa1,
+      0xf9,0x51,0x29,0xe5,
+      0xe5,0x46,0x70,0xf1};
+    uint8_t digest[20];
+    int i;
+
+    st=safe_malloc(sizeof(*st),"sha1_module");
+    st->cl.description="sha1";
+    st->cl.type=CL_HASH;
+    st->cl.apply=NULL;
+    st->cl.interface=&st->ops;
+    st->ops.len=20;
+    st->ops.init=sha1_init;
+    st->ops.update=sha1_update;
+    st->ops.final=sha1_final;
+
+    dict_add(dict,"sha1",new_closure(&st->cl));
+
+    ctx=sha1_init();
+    sha1_update(ctx,testinput,strlen(testinput));
+    sha1_final(ctx,digest);
+    for (i=0; i<20; i++) {
+       if (digest[i]!=expected[i]) {
+           fatal("sha1 module failed self-test\n");
+       }
+    }
+}
diff --git a/site.c b/site.c
index 6101b2bd32f54b5f0d9d842cb14446762d9a87e0..989ad3ee28d925b36fd0ad9151631e962e321508 100644 (file)
--- a/site.c
+++ b/site.c
@@ -15,7 +15,7 @@
 #define DEFAULT_KEY_RENEGOTIATE_GAP 300000 /* Five minutes */
 #define DEFAULT_SETUP_RETRIES 5
 #define DEFAULT_SETUP_TIMEOUT 1000
-#define DEFAULT_WAIT_TIME 10000
+#define DEFAULT_WAIT_TIME 20000
 
 /* Each site can be in one of several possible states. */
 
 #define SITE_SENTMSG5 7
 #define SITE_WAIT     8
 
-#if 0
 static string_t state_name(uint32_t state)
 {
     switch (state) {
-    case 0: return "SITE_STOP";
-    case 1: return "SITE_RUN";
-    case 2: return "SITE_RESOLVE";
-    case 3: return "SITE_SENTMSG1";
-    case 4: return "SITE_SENTMSG2";
-    case 5: return "SITE_SENTMSG3";
-    case 6: return "SITE_SENTMSG4";
-    case 7: return "SITE_SENTMSG5";
-    case 8: return "SITE_WAIT";
+    case 0: return "STOP";
+    case 1: return "RUN";
+    case 2: return "RESOLVE";
+    case 3: return "SENTMSG1";
+    case 4: return "SENTMSG2";
+    case 5: return "SENTMSG3";
+    case 6: return "SENTMSG4";
+    case 7: return "SENTMSG5";
+    case 8: return "WAIT";
     default: return "*bad state*";
     }
 }
-#endif /* 0 */
 
 #define LABEL_MSG0 0x00020200
 #define LABEL_MSG1 0x01010101
@@ -121,6 +119,12 @@ static struct flagstr log_event_table[]={
     { NULL, 0 }
 };
 
+static struct flagstr netlink_option_table[]={
+    { "soft", NETLINK_OPTION_SOFTROUTE },
+    { "allow-route", NETLINK_OPTION_ALLOW_ROUTE },
+    { NULL, 0}
+};
+
 struct site {
     closure_t cl;
     struct site_if ops;
@@ -653,7 +657,8 @@ static bool_t send_msg(struct site *st)
        st->retries--;
        return True;
     } else {
-       slog(st,LOG_SETUP_TIMEOUT,"timed out sending key setup packet");
+       slog(st,LOG_SETUP_TIMEOUT,"timed out sending key setup packet "
+           "(in state %s)",state_name(st->state));
        enter_state_wait(st);
        return False;
     }
@@ -1121,6 +1126,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     struct site *st;
     item_t *item;
     dict_t *dict;
+    uint32_t netlink_options;
 
     st=safe_malloc(sizeof(*st),"site_apply");
 
@@ -1156,9 +1162,10 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->random=find_cl_if(dict,"random",CL_RANDOMSRC,True,"site",loc);
 
     st->privkey=find_cl_if(dict,"local-key",CL_RSAPRIVKEY,True,"site",loc);
-    st->remoteport=dict_read_number(dict,"port",True,"site",loc,0);
-
     st->address=dict_read_string(dict, "address", False, "site", loc);
+    if (st->address)
+       st->remoteport=dict_read_number(dict,"port",True,"site",loc,0);
+    else st->remoteport=0;
     dict_read_subnet_list(dict, "networks", True, "site", loc,
                          &st->remotenets);
     st->pubkey=find_cl_if(dict,"key",CL_RSAPUBKEY,True,"site",loc);
@@ -1193,12 +1200,14 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->log_events=string_list_to_word(dict_lookup(dict,"log-events"),
                                       log_event_table,"site");
 
+    netlink_options=string_list_to_word(dict_lookup(dict,"netlink-options"),
+                                       netlink_option_table,"site");
+
     st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5,
                            "site_apply");
     sprintf(st->tunname,"%s<->%s",st->localname,st->remotename);
 
     /* The information we expect to see in incoming messages of type 1 */
-    /* XXX fix this bit for unaligned access */
     st->setupsiglen=strlen(st->remotename)+strlen(st->localname)+8;
     st->setupsig=safe_malloc(st->setupsiglen,"site_apply");
     put_uint32(st->setupsig+0,LABEL_MSG1);
@@ -1228,7 +1237,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
                                         site_outgoing, st,
                                         st->transform->max_start_pad+(4*4),
                                         st->transform->max_end_pad,
-                                        (st->address!=NULL), st->tunname);
+                                        netlink_options, st->tunname);
     if (!st->netlink_cid) {
        Message(M_WARNING,"%s: netlink device did not let us register "
                "our remote networks. This site will not start.\n",