Hello, I just made available a beta version of a port scan detector that I''ve been working on. The program, called Abacus Sentry, is a port scan/probe detector that offers what I think are a number of unique and useful features: - Runs on TCP or UDP sockets. Configurable by the user to bind to multiples of sockets for increased detection coverage. - Adjustable scan detection value with "state" engine to track past host connections and alarm when a threshold of connections is past. - The ability to react to a port sweep in real time. Abacus Sentry will take any of the following actions when a port sweep is detected: - Add the target host to the local Linux filter list using ipfwadm. - Drop the route to the target host via the route command. - Add the target host to the local TCP wrappers hosts.deny file. - Execute an external program. - Fully log the attacking host IP and port numbers to syslog. - Uses essentially zero system resources when running. - It''s Free. The software was developed on Linux, but uses code that is portable to many platforms. It has been tested on Linux, BSDI, and should compile on most BSD variants. I have personally tested it on Solaris 2.5.1, but there is a mod you need to make because of the use of snprintf''s within the code (I will include a snprintf function in later releases). The code is currently in Beta and is version 0.08 at the time of this writing. The code is available for testing now and I''m especially looking for code reviews and suggestions to improve it. You can find the program at: http://www.psionic.com/abacus/abacus_sentry.html Thanks, -- Craig http://www.psionic.com Here is the configuration file to give you the idea of the options available: # Abacus Sentry Configuration # # $Id: abacus_sentry.conf,v 1.8 1997/12/05 07:31:38 crowland Exp crowland $ # # IMPORTANT NOTE: You CAN NOT put spaces between your port arguments. # Be sure your DEAD_ROUTE points to a local subnet address that is dead. # # The default ports will catch a large number of common probes # # All entries must be in quotes. ####################### # Port Configurations # ####################### # # # Some example port configs # # I like to always keep some ports at the "low" end of the spectrum. # This will detect a sequential port sweep really quickly and usually # these ports are not in use (i.e. tcpmux port 1) # # Un-comment these if you are really anal: #TCP_PORTS="1,7,9,11,15,70,79,80,109,110,111,119,138,139,143,512,513,514,515,540,2000,2001,4000,4001,6000,6001,6667,32771,32772,32773,32774,31337" #UDP_PORTS="1,7,9,66,67,68,69,111,137,138,161,162,474,513,517,518,635,640,641,666,700,2049,32770,32771,32772,32773,32774" # # Use these if you just want to be aware: TCP_PORTS="1,11,15,79,119,143,540,2000,6000,6667,31337,32771,32772,32773,32774" UDP_PORTS="1,7,9,69,161,162,513,635,640,641,700,32770,32771,32772,32773,32774" # # Use these for just bare-bones #TCP_PORTS="1,11,15,143,540,2000,6000,32771,32772,32773,32774" #UDP_PORTS="1,7,9,69,161,162,513,640,700,32770,32771,32772,32773,32774" ###################### # Configuration Files# ###################### # # Hosts to ignore IGNORE_FILE="/usr/local/abacus/abacus_sentry.ignore" # Hosts that have been denied BLOCKED_FILE="/usr/local/abacus/abacus_sentry.blocked" ################### # Response Options# ################### # Options to dispose of attacker. Each is an action that will # be run if an attack is detected. If you don''t want a particular # option then comment it out and it will be skipped. # # The variable $TARGET$ will be substituted with the target attacking # host when an attack is detected. # ################### # Dropping Routes:# ################### # This command is used to drop the route or add the host into # a local filter table. # # If you are going to use the route command to do this you MUST # MAKE SURE THE GATEWAY IS A DEAD HOST (333.444.555,666) on the # local network or you may get bizarre results on the local segment. # Generic Linux KILL_ROUTE="/sbin/route add -host $TARGET$ gw 333.444.555.666" # Generic BSD (BSDI) #KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" # Generic Sun #KILL_ROUTE="/usr/sbin/route add $TARGET$ 333.444.555.666 1" # Generic #KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" # For those of you running Linux with ipfwadm installed you may like # this better as it drops the host into the packet filter. # You can only have one KILL_ROUTE turned on at a time though. # If you want both (why?) then add this command to the KILL_RUN_CMD # section. This I think is the best method for Linux hosts. # #KILL_ROUTE="/sbin/ipfwadm -I -i deny -S $TARGET$ -o ############### # TCP Wrappers# ############### # This text will be dropped into the hosts.deny file for wrappers # to use. There are two formats for TCP wrappers: # # Format One: Old Style - The default when extended host processing # options are not enabled. # KILL_HOSTS_DENY="ALL: $TARGET$" # # Format Two: New Style - The format used when extended option # processing is enabled. You can drop in extended processing # options, but be sure you escape all ''%'' symbols with a backslash # to prevent problems writing out (i.e. \%c \%h ) # #KILL_HOSTS_DENY="ALL: $TARGET$ : DENY" ################### # External Command# ################### # This is a command that is run when a host connects, it can be whatever # you want it to be (ping of death, winnuke, death threat, etc.), but # use this with caution. This command is executed before the route is # dropped to ensure that your "package" is delivered whatever that may # be. It is disabled by default. # #KILL_RUN_CMD="/some/path/here/ping_of_death $TARGET$" ##################### # Scan trigger value# ##################### # Enter in the number of port connects you will allow before an # alarm is given. The default is 0 which will react immediately. # A value of 1 or 2 will reduce false alarms. Anything higher is # probably not necessary. This value must always be specified. # SCAN_TRIGGER="0" ###################### # Port Banner Section# ###################### # # Enter text in here you want displayed to a person tripping the Sentry. # I *don''t* recommend taunting the person as this will aggravate them. # Leave this commented out to disable the feature # #PORT_BANNER="** UNAUTHORIZED ACCESS PROHIBITED *** Administrators alerted to your connection. Go Away." # EOF
ther
1997-Dec-07 14:01 UTC
Re: [linux-security] New Program: Abacus Sentry - Port Scan Detector
On Fri, 5 Dec 1997, Craig H. Rowland wrote:> - Add the target host to the local Linux filter list using > ipfwadm. > > - Drop the route to the target host via the route command.abacus (as described) would kill all connections to a host which portscans him. so an attacker would be able to kill any connection to any host by spoofed UDP packages. bye, therapy
Michael H. Warfield
1997-Dec-09 14:29 UTC
Re: [linux-security] Re: New Program: Abacus Sentry - Port Scan Detector
ther enscribed thusly:> On Fri, 5 Dec 1997, Craig H. Rowland wrote: > > > - Add the target host to the local Linux filter list using > > ipfwadm. > > > > - Drop the route to the target host via the route command. > abacus (as described) would kill all connections to a host which > portscans him. so an attacker would be able to kill any connection to > any host by spoofed UDP packages.Interesting point. Triggering any sort of defensive action based on UDP would open up a new class of denial of service attacks... Solution: Block all UDP anyways, outside of very selected connections such as to your nameservers and timeservers. There isn''t much UDP of real use outside of those anyways. I totally block all UDP at my firewalls (yeah RealAudio gets toasted - tough). DNS and NTP get handled by specific hosts so I can keep tight control over their access. Then you can set up the kill by route action to be triggered only on TCP port scanning, which would then require fully connected sessions. Since Linux is NOT sequence number predictable, an attacker would play hell spoofing a port scan via TCP. However... Because accept behaves differently on Linux than on many other flavors of UNIX, you would have to be extremely careful not to let failed connections trigger the kill behavior though. On Linux, if someone does a "stealth scan" of the TCP ports by issuing a raw "SYN" packet but then resets the connection before a session is fully connected, the accept still returns a socket. You subsequently get a SIGPIPE when you try and access the socket. This caused some entertaining headaches with inetd crashing if the internal services such as "time" got stealth scanned. If this problem also exists in abacus, this would allow a knowledgable attacker to spoof a TCP steath scan and cause the same sort of denial of service as spoofing a UDP scan. This is worth checking out and specifically avoiding if the avoidance code does not already exist. I''ve downloaded abacus but have not had the time to look at the code as yet. The possibilities of creating denial of service attacks like this, open up some new interest though...> bye, > therapy > > -- > ---------------------------------------------------------------------- > Please refere to the information about this list as well as general > information about Linux security at http://www.aoy.com/Linux/Security. > ---------------------------------------------------------------------- > > To unsubscribe: mail -s unsubscribe test-list-request@redhat.com < /dev/nullMike -- Michael H. Warfield | (770) 985-6132 | mhw@WittsEnd.com (The Mad Wizard) | (770) 925-8248 | http://www.wittsend.com/mhw/ NIC whois: MHW9 | An optimist believes we live in the best of all PGP Key: 0xDF1DD471 | possible worlds. A pessimist is sure of it!
Craig H. Rowland
1997-Dec-10 14:57 UTC
Re: [linux-security] Re: Re: New Program: Abacus Sentry - Port Scan Detector
-----BEGIN PGP SIGNED MESSAGE----- On Tue, 9 Dec 1997, Michael H. Warfield wrote:> ther enscribed thusly: > > On Fri, 5 Dec 1997, Craig H. Rowland wrote: > > > > > - Add the target host to the local Linux filter list using > > > ipfwadm. > > > > > > - Drop the route to the target host via the route command. > > abacus (as described) would kill all connections to a host which > > portscans him. so an attacker would be able to kill any connection to > > any host by spoofed UDP packages. > > Interesting point. Triggering any sort of defensive action based > on UDP would open up a new class of denial of service attacks...This unfortunately is true and is a known issue with the program. Version 0.09 (on the website now) has an option called DO_IGNORE_UDP that will disable all actions (except reporting the connection) for all UDP sweeps.> Then you can set up the kill by route action to be triggered > only on TCP port scanning, which would then require fully connected > sessions. Since Linux is NOT sequence number predictable, an attacker > would play hell spoofing a port scan via TCP.Also the program has two modes: -tcp and -udp. The program was split into these two functions to provide for failures and to allow the end user to only monitor for one type of port probing activity.> > However... > > Because accept behaves differently on Linux than on many other > flavors of UNIX, you would have to be extremely careful not to let failed > connections trigger the kill behavior though. On Linux, if someone does > a "stealth scan" of the TCP ports by issuing a raw "SYN" packet but then > resets the connection before a session is fully connected, the accept > still returns a socket. You subsequently get a SIGPIPE when you try and > access the socket. This caused some entertaining headaches with inetd > crashing if the internal services such as "time" got stealth scanned. > If this problem also exists in abacus, this would allow a knowledgable > attacker to spoof a TCP steath scan and cause the same sort of denial > of service as spoofing a UDP scan.I should be catching SIGPIPE, if I''m not please let me know. Stealth scanning brings in some other issues for a port scan detector. This is one of the reasons I don''t include this feature. Here are a few points: 1) False alarm rates increase due to normal anomalies in network traffic that may result in connections that appear to be "half-open" but really resulted from some type of transient failure during initial negotiation. 2) A properly designed stealth port scan monitor should contain a state engine to monitor when connections go into a fully-established state. For a busy server this adds a resource expense that I think is not really warranted compared to the relative rarity of a port sweep versus normal network activity. 3) The inclusion of stealth scan detection will mostly likely require non-portable code and this was counter to one of the design goals.> > This is worth checking out and specifically avoiding if the > avoidance code does not already exist. > > I''ve downloaded abacus but have not had the time to look at the > code as yet. The possibilities of creating denial of service attacks > like this, open up some new interest though... >I have changed the documentation to state that UDP scan detection should have automatic actions disabled if it is an Internet connected site. Internal hosts should probably keep it enabled; if they have a host spoofing packets internally you have a bigger problem to worry about. This is very early release code and I am looking for code reviews. If anyone would like to participate in this I would be happy to have you. I''ve received a large number of comments to date and have acted/responded to each. If anyone has any comments I''m happy to answer them.> > bye, > > therapy > > > > -- > > ---------------------------------------------------------------------- > > Please refere to the information about this list as well as general > > information about Linux security at http://www.aoy.com/Linux/Security. > > ---------------------------------------------------------------------- > > > > To unsubscribe: mail -s unsubscribe test-list-request@redhat.com < /dev/null > > Mike > -- > Michael H. Warfield | (770) 985-6132 | mhw@WittsEnd.com > (The Mad Wizard) | (770) 925-8248 | http://www.wittsend.com/mhw/ > NIC whois: MHW9 | An optimist believes we live in the best of all > PGP Key: 0xDF1DD471 | possible worlds. A pessimist is sure of it! > > -- >- -- Craig -----BEGIN PGP SIGNATURE----- Version: 2.6.2i iQCVAwUBNI6t0K5kS8WYq/59AQHV3wP9HDPHmiMVWEUdKwBVeFpVd/sZtbpZNgfp VG251lJAMxpnQa1BAoDTpdZDCGGBFYmh18ZSIBa5tSJaZiihhxq7b0G/aYz3GKGY fSb5HuoIBss7+7bEWKNKJP7Y45SNbiiZ5T/ucUyDFep94+hIiEp/MApohONa4JaY BwGHEtaBhuE=jNnt -----END PGP SIGNATURE----- From mail@mail.redhat.com Dec 17:48:20 1997 (EST) -0500 Received: (qmail 2324 invoked from network); 10 Dec 1997 22:42:49 -0000 Received: from ding.yuriev.com (HELO ding.mailhub.com) (207.106.66.2) by mail2.redhat.com with SMTP; 10 Dec 1997 22:42:49 -0000 Received: (from alex@localhost) by ding.mailhub.com (8.8.7/8.8.5) id RAA29594; Wed, 10 Dec 1997 17:48:20 -0500 (EST) Received: from mail2.redhat.com (mail2.redhat.com [199.183.24.247]) by ding.mailhub.com (8.8.7/8.8.5) with SMTP id RAA14362 for <alex@yuriev.com>; Wed, 10 Dec 1997 17:25:07 -0500 (EST) Received: (qmail 17016 invoked by uid 501); 10 Dec 1997 22:19:34 -0000 Received: (qmail 17004 invoked from network); 10 Dec 1997 22:19:33 -0000 Received: from mothra.trimble.com (206.40.88.20) by mail2.redhat.com with SMTP; 10 Dec 1997 22:19:33 -0000 Received: from Trimble.COM by mothra.Trimble.COM (SMI-8.6/SMI-SVR4) id OAA10009; Wed, 10 Dec 1997 14:22:15 -0800 Received: from thoth.trimble.co.nz by Trimble.COM (SMI-8.6/SMI-SVR4) id OAA26929; Wed, 10 Dec 1997 14:20:02 -0800 Received: from crom.trimble.co.nz (root@crom.trimble.co.nz [155.63.248.24]) by thoth.trimble.co.nz (8.8.5/8.8.3) with ESMTP id LAA02696 for <linux-security@redhat.com>; Thu, 11 Dec 1997 11:37:45 +1300 Received: from crom.trimble.co.nz (jhaar@localhost [127.0.0.1]) by crom.trimble.co.nz (8.8.7/8.8.3) with ESMTP id LAA14312; Thu, 11 Dec 1997 11:18:53 +1300 Message-Id: <199712102218.LAA14312@crom.trimble.co.nz> X-Mailer: exmh version 2.0zeta 7/24/97 From: Jason Haar <Jason.Haar@trimble.co.nz> Approved: alex@yuriev.com To: linux-security@redhat.com Cc: ewt@redhat.com, marc@redhat.com X-Face: 4$O4b"ro`%m8%(^R{*>`x8LS`97,DOrtRGXnwv?T\MaE>9cMIU>NY")XSUIwi-~I;q*H&f% |KyQWgQOQ?^G&iIHM"`Wp|idCFvO\L",^*&_`2">W83Ucj;,\n|zq''G9@5E?-n-j|jwH`*Mc`[t>*Q Z^ X-pgp-fingerprint: E6 83 E4 BB 47 6B FD D8 26 8B BA 9C 0D 4D 42 2C Subject: The never ending story of buffer overruns in cron(tab) Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Date: Thu, 11 Dec 1997 11:18:53 +1300 Sender: Jason.Haar@trimble.co.nz [Mod: FYI - i have not looked at it yet. I am not going to approve anything on this topic until someone from redhat comments on it or until someone really goes though the code -- alex] [Mod: ewt@redhat.com and marc@redhat.com added to the Cc: list -- alex] Hi there I saw there was a new redhat vixie-cron available (vixie-cron-3.0.1-19.src.rpm, dated Nov 10), and given the number of buffer overruns I''ve seen this year in everything, I downloaded it to have a look. It looks very similar to the one I was running (cron-3.0pl1), so I dug deeper... They both still appear to use sprintf instead of snprintf''s everywhere! I went back through my old BUGTRAQ/Linux Security mail messages of the past year, and there were several pointing out sprintf problems in cron back in Dec last year. Sure enough, none of those had shown up in the "newer" crons! I have patched them into my current cron now, but can someone tell me why is it still like this? Surely all new cron* distributions should have such holes fixed by now? Cheers -- Jason Haar, Unix/Networking Specialist, Trimble Navigation New Zealand Phone: +64 3 3391377 Fax: +64 3 3391417 From mail@mail.redhat.com Dec 12:35:13 1997 (EST) -0500 Received: (qmail 31971 invoked from network); 22 Dec 1997 17:29:35 -0000 Received: from ding.yuriev.com (HELO ding.mailhub.com) (207.106.66.2) by mail2.redhat.com with SMTP; 22 Dec 1997 17:29:35 -0000 Received: (from alex@localhost) by ding.mailhub.com (8.8.7/8.8.5) id MAA28915; Mon, 22 Dec 1997 12:35:13 -0500 (EST) Received: from mail2.redhat.com (mail2.redhat.com [199.183.24.247]) by ding.mailhub.com (8.8.7/8.8.5) with SMTP id GAA06835 for <alex@yuriev.com>; Wed, 17 Dec 1997 06:25:13 -0500 (EST) Received: (qmail 9454 invoked by uid 501); 17 Dec 1997 11:19:34 -0000 Received: (qmail 9442 invoked from network); 17 Dec 1997 11:19:32 -0000 Received: from chiark.greenend.org.uk (root@195.224.76.132) by mail2.redhat.com with SMTP; 17 Dec 1997 11:19:32 -0000 Received: by chiark.greenend.org.uk id m0xiHVQ-0004oIC (Debian /\oo/\ Smail3.1.29.1 #29.37); Wed, 17 Dec 97 11:19 GMT Message-Id: <m0xiHVQ-0004oIC@chiark.greenend.org.uk> Date: Wed, 17 Dec 97 11:19 GMT From: userv-maint@chiark.greenend.org.uk (Ian Jackson) Approved: alex@yuriev.com To: linux-security@redhat.com Subject: userv - how to make cron (et al) not setuid In-Reply-To: <199712162157.WAA00510@adder.et.tudelft.nl> References: <m0xhZYW-0004ogC@chiark.greenend.org.uk> <199712162157.WAA00510@adder.et.tudelft.nl> [Mod: chat removed -- alex] From: userv-maint@chiark.greenend.org.uk (Ian Jackson) Approved: alex@yuriev.com To: linux-security@redhat.com Subject: userv - how to make cron (et al) not setuid 0. Introduction Some time ago I posted on linux-security to say that I was working on a client/server pair which would allow you to invoke a privileged service in a more secure manner. I''ve now completed this, and it''s been service by way of alpha-test on my own system for some time, implementing user-provided CGI scripts (which run as the user). Perhaps I should explain some more. There are a number of places on a Unix system where program invocations cross security boundaries. At the moment this is handled using setuid programs, which has a number of disadvantages: you can''t easily draw the security boundary where you want it; the setuid program really has to be written in C (with attendant buffer overrun problems et al); and sanitising the environment (not just the environment variables, but things like umasks, ulimits, and all the other guff that children inherit) to make it safe to execute is very difficult. My replacement works as follows: you design your program so that the security boundary is exactly at a program invocation, and replace the setuid call with a call via my `userv'' facility. userv is responsible for properly enforcing the security boundary, so that the called parts of your program can trust their PATH, ulimits, controlling tty, et al. userv has to be secure, but you only have to write it once. Furthermore, the client/server model means you don''t have to worry so much about resetting all the many inherited properties of processes. 1. Applications The applications are many. A lot of Unix facilities have mutually untrusting security domains (users) calling each others'' programs. For example, mail and lpr (with deferred printing) want to be able to access the users'' files without trusting those users, but the user doesn''t want to trust them more than they have to. Below I''ll describe an example of how to use userv to implement a more secure cron/at daemon. I haven''t actually gotten round to writing this yet, mainly because it seemed an uninteresting problem - a few fairly trivial Perl scripts. Compare that to the onerous task of writing a secure cron subsystem in C using only conventional setuid calls ... 3. Configuration userv has a configuration language which allows users (and the system administrator on behalf of users) to control which commands can be executed as them and exactly how, by which other users. Details about the invoking user are also passed to the executed program in special USERV_... environment variables, so that it can implement its own access control or other features. The configuration scheme has the facility for system administrators to provide default configurations of services, which the user can override, or for the sysadmin to provide `mandatory'' services. For example, a sysadmin might wish to be able to securely remove old files in /var/tmp, and could enforce the provision of a `remove this file in /var/tmp'' facility to some semi-trusted set of operator users. 4. Limitations userv does not solve all security problems with privileged programs. For example, privileged programs written in C invoked by userv will still be vulnerable to any buffer overruns in their argument parsing (if userv is configured to allow arguments to be passed to them). However, with userv it is no longer necessary to use C for many privileged programs. It is much easier to make a correct and secure privileged program if it can be written in a language like Perl, Python or whatever. userv is also not suitable for all applications. Situations where the privileged program needs (for example) to make ioctls on the controlling tty cannot be resolved using userv, because userv isolates the called program from the caller''s tty. Programs like `really'' which are intended to allow already-trusted accounts (which are not root to avoid mistakes rather than intrusion) to become root are not appropriate for userv, because userv services do not inherit the caller''s environment. 5. URL Anyway, userv has a WWW page: <URL:http://www.chiark.greenend.org.uk/~ian/userv/> userv is short for `user services'', and is pronounced `you-serve''. 6. cron example Here I will describe the overall design of a cron subsystem which has a much smaller probability of having security bugs than a conventional cron. Because userv is used to deal with many of the security issues, the design can concentrate on scheduling (cron''s ostensible task) rather than security. The system is even extensible - new crontab formats with enhanced semantics can be introduced without needing to touch the system-provided core. The system is split into two security areas: the user-side utilities, and a system-provided daemon which calls back the user when the time for a job has been reached. The daemon runs as `cron''; there is no root component except userv. The daemon maintains in core a list of times at which each user wanted to be called back. It repeatedly sleeps until the next time has been reached, and then it invokes userv <user> cron/callback <execution-time> The system configuration files for userv are arranged so that the `cron'' user (and no other) can run cron/callback as any user, and that this causes the program /usr/lib/cron/services/callback to be run. This program checks that the current time is `close enough'' to the time specified on its command line, scans the user''s crontab file, kept in ~/.userv/.servdata/cron/crontab (not in /var), and executes each command which needs to be run that minute. It deals with mailing the output (if any) back to the user. How does the user update their crontab ? They call the `crontab'' command, in /usr/bin, as usual. This program stores the new crontab in ~/.userv/.servdata/cron/crontab, and invokes /usr/lib/cron/services/tellcron. /usr/lib/cron/services/tellcron runs userv cron cron/update <horizon-time> feeding it on standard input a list of times before <horizon-time> (a unix time in decimal) when the user wants to be woken up (this data being derived from ~/.userv/.servdata/cron/crontab). The <horizon-time> would be some fixed time (an hour, perhaps) from now. The userv configuration arranges for any user to be able to run /usr/lib/cron/services/update as cron by calling cron/update. The update script checks that the calling user (in $ENV{''USERV_USER''}) is allowed to use cron, checks the syntax of the input, and writes the data (with the name of the user) to a named pipe in /var/run/cron (some locking is required here, and formatting of the data in a way that means that partial writes are not accepted and do not interfere with the next update''s write). It reads the pipe to wait for the cron daemon''s reply. At system startup the cron daemon runs userv <user> cron/tellcron as each user who is allowed to run cron jobs. This is configured to run /usr/lib/cron/services/tellcron. Note that the system can easily be extended to at jobs, without needing to change the daemon.>From a security point of view, the worst that the cron daemon could dowould be to cause genuine cron jobs to be executed too often or not at all, or to repeatedly request `tellcron''. The worst that a user can do is repeatedly chop and change their list of pending times. The data crossing the security boundary - just timestamps (with usernames supplied by userv) - is very simple and so easy to check and hard to misparse, and the programs can all be written in a straightforward interpreted language. The user''s crontab, whose parsing is complicated, and which contains data which is to be executed, stays entirely within the user''s own security boundary - noone but the user themselves ever needs to touch that data. -- Ian Jackson, at home. Local/personal: ijackson@chiark.greenend.org.uk ian@chiark.greenend.org.uk http://www.chiark.greenend.org.uk/~ijackson/